From 86cceb6b12f8ada1f6dc49879779441a873d56d9 Mon Sep 17 00:00:00 2001 From: "Kang, Shuli" Date: Mon, 16 Apr 2018 13:37:25 -0700 Subject: [PATCH 1/7] Bugfix/homozygous variants can belong to any phase set 3119 (#169) * WIP * WIP * WIP * Implement the fix; Refactoring * Bugfix; Refactoring * reduce the Cognitive complexity * Remove commented code --- Phantom/DataStructures/AlleleIndexBlock.cs | 276 ++++-------------- Phantom/DataStructures/Genotype.cs | 66 +++++ Phantom/DataStructures/GenotypeBlock.cs | 146 +++++++++ Phantom/DataStructures/PositionSet.cs | 152 +++++----- Phantom/Workers/RecomposedAlleleSet.cs | 164 +++++++++++ Phantom/Workers/VariantGenerator.cs | 183 ++---------- .../DataStructures/AlleleIndexBlockTests.cs | 103 +++---- .../DataStructures/PositionSetTests.cs | 6 +- .../Phantom/Workers/VariantGeneratorTests.cs | 14 +- 9 files changed, 595 insertions(+), 515 deletions(-) create mode 100644 Phantom/DataStructures/Genotype.cs create mode 100644 Phantom/DataStructures/GenotypeBlock.cs create mode 100644 Phantom/Workers/RecomposedAlleleSet.cs diff --git a/Phantom/DataStructures/AlleleIndexBlock.cs b/Phantom/DataStructures/AlleleIndexBlock.cs index bbab0651..1ca27ed4 100644 --- a/Phantom/DataStructures/AlleleIndexBlock.cs +++ b/Phantom/DataStructures/AlleleIndexBlock.cs @@ -7,92 +7,90 @@ namespace Phantom.DataStructures public sealed class AlleleIndexBlock : IEquatable { - public List AlleleIndexes { get; } + public int[] AlleleIndexes { get; } public int PositionIndex { get; } - public AlleleIndexBlock(int positionIndex, List alleleIndexes) + public AlleleIndexBlock(int positionIndex, int[] alleleIndexes) { PositionIndex = positionIndex; AlleleIndexes = alleleIndexes; } - - public static Dictionary> GetAlleleIndexBlockToSampleIndex(Dictionary<(string, int), List> genotypeToSample, HashSet[] allelesWithUnsupportedType, int[] starts, List functionBlockRanges) + public static Dictionary> GetAlleleIndexBlockToSampleIndex(Dictionary> genotypeBlockToSample, HashSet[] allelesWithUnsupportedType, int[] starts, List functionBlockRanges) { var alleleIndexBlockToSampleIndex = new Dictionary>(); - foreach (var key in genotypeToSample.Keys) + foreach (var (genotypeBlock, sampleIndexes) in genotypeBlockToSample.ToList()) { - var (genotypes, startIndexInBlock) = key; - var sampleIndexes = genotypeToSample[key]; - //var alleleIndexBlocks = new List(); - int ploidy = GetMaxPloidy(genotypes); - GenotypeBlock currentBlockInfo = null; - var genotypeArray = genotypes.Split(";"); - var lastNonRefPositions = new int[ploidy]; - for (int i = 0; i < ploidy; i++) lastNonRefPositions[i] = -1; + var genotypeArray = genotypeBlock.Genotypes.ToArray(); + int startIndexInBlock = genotypeBlock.PosIndex; + int ploidy = GetMaxPloidy(genotypeArray); + var currentSubBlockStart = -1; + for (int i = 0; i < genotypeArray.Length; i++) { int indexInBlock = i + startIndexInBlock; var genotype = genotypeArray[i]; - var genotypeIndexes = genotype?.Split('/', '|').ToArray(); + var genotypeIndexes = genotype.AlleleIndexes; bool isRefPosition = IsRefPosition(genotypeIndexes); - bool blockTerminationConditionMet = string.IsNullOrEmpty(genotype) || - HasReducedPloidy(genotypeIndexes, ploidy) || + bool blockTerminationConditionMet = HasReducedPloidy(genotypeIndexes, ploidy) || HasUndeterminedOrUnsupportedGenotype(genotypeIndexes, allelesWithUnsupportedType[indexInBlock]) || IsUnphasedHeterozygote(genotype); + // block terminates at this position if (blockTerminationConditionMet) { - if (currentBlockInfo != null) + if (currentSubBlockStart != -1) { - foreach (var subBlock in currentBlockInfo.Split(starts, functionBlockRanges)) - { - UpdateBlockToSampleAlleleMapping(alleleIndexBlockToSampleIndex, subBlock, - sampleIndexes); - } - currentBlockInfo = null; + // Don't include this position + ProcessCurrentSubBlock(genotypeBlock, currentSubBlockStart, i - currentSubBlockStart, + starts, functionBlockRanges, sampleIndexes, ref alleleIndexBlockToSampleIndex); + currentSubBlockStart = -1; } } - else + // current sub block is empty + else if (currentSubBlockStart == -1) { - // current block is empty - if (currentBlockInfo == null) - { - if (isRefPosition) continue; // don't build a block if the first position is ref - currentBlockInfo = GenotypeBlock.CreateWithGenotypes(indexInBlock, genotypeIndexes); - } - else - { - currentBlockInfo.AddAlleleIndexes(genotypeIndexes); - } + if (isRefPosition) continue; // don't build a block if the first position is ref + currentSubBlockStart = i; } } - if (currentBlockInfo != null) + if (currentSubBlockStart != -1) { - foreach (var subBlock in currentBlockInfo.Split(starts, functionBlockRanges)) - { - UpdateBlockToSampleAlleleMapping(alleleIndexBlockToSampleIndex, subBlock, - sampleIndexes); - } + ProcessCurrentSubBlock(genotypeBlock, currentSubBlockStart, genotypeArray.Length - currentSubBlockStart, + starts, functionBlockRanges, sampleIndexes, ref alleleIndexBlockToSampleIndex); } } return alleleIndexBlockToSampleIndex; } - private static bool IsRefPosition(string[] genotypeIndexes) => genotypeIndexes.All(x => x == "0"); + private static void ProcessCurrentSubBlock(GenotypeBlock entireGenotypeBlock, int currentSubBlockStart, int currentSubBlockSize, int[] starts, List functionBlockRanges, List sampleIndexes, ref Dictionary> alleleIndexBlockToSampleIndex) + { + GenotypeBlock currentSubBlock = + entireGenotypeBlock.GetSubBlock(currentSubBlockStart, currentSubBlockSize); + foreach (var functionBlock in currentSubBlock.Split(starts, functionBlockRanges)) + { + UpdateBlockToSampleAlleleMapping(alleleIndexBlockToSampleIndex, functionBlock, + sampleIndexes); + } + } + + private static bool IsRefPosition(int[] genotypeIndexes) => genotypeIndexes.All(x => x == 0); private static void UpdateBlockToSampleAlleleMapping(Dictionary> alleleIndexBlockToSampleAllele, GenotypeBlock genotypeBlock, List sampleIndexes) { if (genotypeBlock == null) return; - var genotypeIndexBlocks = genotypeBlock.Genotypes; - for (int alleleIndex = 0; alleleIndex < genotypeIndexBlocks.Length; alleleIndex++) + var genotypes = genotypeBlock.Genotypes.ToArray(); + if (genotypes.Length <= 1) return; + int ploidy = genotypes[0].AlleleIndexes.Length; + for (int haplotypeIndex = 0; haplotypeIndex < ploidy; haplotypeIndex++) { - var genotypeIndexBlock = genotypeIndexBlocks[alleleIndex].ToList(); - if (genotypeIndexBlock.Count <= 1) continue; - var alleleIndexBlock = new AlleleIndexBlock(genotypeBlock.PosIndex, genotypeIndexBlock); - var currentSampleAlleles = GetSampleAlleles(sampleIndexes, (byte)alleleIndex); + var alleleIndexes = new int[genotypes.Length]; + for (var i = 0; i < genotypes.Length; i++) alleleIndexes[i] = genotypes[i].AlleleIndexes[haplotypeIndex]; + var alleleIndexBlock = new AlleleIndexBlock(genotypeBlock.PosIndex, alleleIndexes); + + var currentSampleAlleles = GetSampleAlleles(sampleIndexes, (byte)haplotypeIndex); if (alleleIndexBlockToSampleAllele.TryGetValue(alleleIndexBlock, out var sampleAlleles)) { @@ -108,23 +106,15 @@ private static void UpdateBlockToSampleAlleleMapping(Dictionary GetSampleAlleles(List sampleIndexes, byte alleleIndex) { var sampleAlleleList = new SampleAllele[sampleIndexes.Count]; - for (int i = 0; i < sampleIndexes.Count; i++) + for (var i = 0; i < sampleIndexes.Count; i++) { sampleAlleleList[i] = new SampleAllele(sampleIndexes[i], alleleIndex); } return sampleAlleleList.ToList(); } - internal static int GetMaxPloidy(string genotypes) - { - int ploidy = -1; - foreach (var genotype in genotypes.Split(";")) - { - var currentPloidy = string.IsNullOrEmpty(genotype) || genotype == "." ? 0 : genotype.Split('|', '/').Length; - if (currentPloidy > ploidy) ploidy = currentPloidy; - } - return ploidy; - } + internal static int GetMaxPloidy(IEnumerable genotypes) => genotypes.Select(x => x.AlleleIndexes.Length).Max(); + internal static void TrimTrailingRefAlleles(List[] blocks) { @@ -136,15 +126,11 @@ internal static void TrimTrailingRefAlleles(List[] blocks) } } - private static bool HasReducedPloidy(string[] genotypeIndexes, int blockPloidy) => genotypeIndexes.Length < blockPloidy; + private static bool HasReducedPloidy(int[] genotypeIndexes, int blockPloidy) => genotypeIndexes.Length < blockPloidy; - private static bool HasUndeterminedOrUnsupportedGenotype(string[] genotypeIndexes, HashSet indexOfSkippedTypes) => genotypeIndexes.Any(x => x == "." || indexOfSkippedTypes.Contains(x)); + private static bool HasUndeterminedOrUnsupportedGenotype(int[] genotypeIndexes, HashSet indexOfSkippedTypes) => genotypeIndexes.Any(x => x == -1 || indexOfSkippedTypes.Contains(x)); - private static bool IsUnphasedHeterozygote(string genotype) - { - var alleleIndexes = genotype.Split('/'); - return alleleIndexes.Length > 1 && alleleIndexes.Distinct().Count() > 1; - } + private static bool IsUnphasedHeterozygote(Genotype genotype) => !genotype.IsPhased && !genotype.IsHomozygous; public bool Equals(AlleleIndexBlock other) => PositionIndex == other.PositionIndex && AlleleIndexes.SequenceEqual(other.AlleleIndexes); @@ -152,165 +138,13 @@ public override int GetHashCode() { unchecked { - var hashCode = PositionIndex.GetHashCode(); - AlleleIndexes.ForEach(x => hashCode = (hashCode * 1201) ^ x.GetHashCode()); - return hashCode; - } - } - } - - public sealed class GenotypeBlock - { - public readonly int PosIndex; - public readonly List[] Genotypes; - - private GenotypeBlock(int posIndex, int ploidy) - { - PosIndex = posIndex; - Genotypes = new List[ploidy]; - for (var i = 0; i < ploidy; i++) Genotypes[i] = new List(); - } - - private GenotypeBlock(int posIndex, List[] genotypes) - { - PosIndex = posIndex; - Genotypes = genotypes; - } - - public static GenotypeBlock CreateWithGenotypes(int posIndex, string[] genotypeIndexes) - { - var blockInfo = new GenotypeBlock(posIndex, genotypeIndexes.Length); - blockInfo.AddAlleleIndexes(genotypeIndexes); - return blockInfo; - } - - public void AddAlleleIndexes(string[] genotypeIndexes) - { - int ploidy = genotypeIndexes.Length; - if (ploidy != Genotypes.Length) throw new Exception($"The ploidy of provided genotype indexes ({ploidy}) doesn't that of the block ({Genotypes.Length}) "); - for (var alleleIndex = 0; alleleIndex < ploidy; alleleIndex++) - Genotypes[alleleIndex].Add(int.Parse(genotypeIndexes[alleleIndex])); - } - - public IEnumerable Split(int[] starts, List functionBlockRanges) - { - int numBreaks = Genotypes[0].Count - 1; - if (numBreaks == 0) return new List(); - int ploidy = Genotypes.Length; - var breaks = new bool[ploidy][]; - for (int i = 0; i < ploidy; i++) - { - breaks[i] = GetAlleleBreaks(Genotypes[i], starts, functionBlockRanges); - } - bool[] finalBreaks = GetFinalBreaks(breaks); - return GetGenotypeBlocks(finalBreaks); - } - - private bool[] GetAlleleBreaks(List alleleGenotypes, int[] starts, List functionBlockRanges) - { - int numBreaks = Genotypes[0].Count - 1; - var alleleBreaks = new bool[numBreaks]; - int lastNonRefPosition = -1; - - // function block checking - for (int gtIndex = 0; gtIndex < Genotypes[0].Count; gtIndex++) - { - int genotype = alleleGenotypes[gtIndex]; - if (genotype == 0) - { - // check leading ref positions - if (gtIndex == 0 || alleleBreaks[gtIndex - 1] && gtIndex < numBreaks) - { - alleleBreaks[gtIndex] = true; - } - // check tailing ref positions - else if (gtIndex == numBreaks) - { - MakeBreakAndCheckTailingRefPositions(gtIndex - 1, alleleBreaks, alleleGenotypes); - } - } - else - { - if (gtIndex > 0) - { - bool outOfRange = lastNonRefPosition != -1 && starts[PosIndex + gtIndex] > functionBlockRanges[lastNonRefPosition]; - if (outOfRange) - MakeBreakAndCheckTailingRefPositions(gtIndex - 1, alleleBreaks, alleleGenotypes); - } - lastNonRefPosition = PosIndex + gtIndex; - } - } - return alleleBreaks; - } - - private static void MakeBreakAndCheckTailingRefPositions(int breakIndex, bool[] alleleBreaks, List alleleGenotypes) - { - alleleBreaks[breakIndex] = true; - // check "tailing" referece positions before this break - for (int i = breakIndex - 1; i >= 0; i--) - { - // stop when the break bool is true or the allele is non-ref - if (alleleBreaks[i] || alleleGenotypes[i + 1] != 0) break; - alleleBreaks[i] = true; - } - } - - private List GetGenotypeBlocks(bool[] finalBreaks) - { - var subGenotypeBlocks = new List(); - int subBlockStart = 0; - int subBlockEnd = 0; - for (int i = 0; i < finalBreaks.Length; i++) - { - if (!finalBreaks[i]) - { - subBlockEnd++; - } - else - { - int subBlockSize = subBlockEnd - subBlockStart + 1; - if (subBlockSize > 1) - { - subGenotypeBlocks.Add(GetSubBlock(subBlockStart, subBlockEnd)); - } - subBlockStart = i + 1; - subBlockEnd = i + 1; - } - } - if (subBlockEnd - subBlockStart >= 1) subGenotypeBlocks.Add(GetSubBlock(subBlockStart, subBlockEnd)); - return subGenotypeBlocks; - } - - private GenotypeBlock GetSubBlock(int subBlockStart, int subBlockEnd) - { - int newPosIndex = PosIndex + subBlockStart; - int ploidy = Genotypes.Length; - int numPositions = subBlockEnd - subBlockStart + 1; - var newGenotypes = new List[ploidy]; - for (int i = 0; i < ploidy; i++) - { - newGenotypes[i] = new ArraySegment(Genotypes[i].ToArray(), subBlockStart, numPositions).ToList(); - } - return new GenotypeBlock(newPosIndex, newGenotypes); - } - - private static bool[] GetFinalBreaks(bool[][] breaks) - { - int ploidy = breaks.Length; - int numBreaks = breaks[0].Length; - bool[] finalBreaks = new bool[numBreaks]; - for (int i = 0; i < numBreaks; i++) - { - bool anyFalse = false; - for (int j = 0; j < ploidy; j++) + int hashCode = PositionIndex.GetHashCode(); + foreach (int alleleIndex in AlleleIndexes) { - if (breaks[j][i]) continue; - anyFalse = true; - break; + hashCode = (hashCode * 1201) ^ alleleIndex.GetHashCode(); } - if (!anyFalse) finalBreaks[i] = true; + return hashCode; } - return finalBreaks; } } } \ No newline at end of file diff --git a/Phantom/DataStructures/Genotype.cs b/Phantom/DataStructures/Genotype.cs new file mode 100644 index 00000000..5d648d05 --- /dev/null +++ b/Phantom/DataStructures/Genotype.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; + +namespace Phantom.DataStructures +{ + public sealed class Genotype : IEquatable + { + public readonly int[] AlleleIndexes; + public readonly bool IsPhased; + public readonly bool IsHomozygous; + + public Genotype(int[] alleleIndexes, bool isPhased) + { + AlleleIndexes = alleleIndexes; + IsPhased = isPhased; + IsHomozygous = GetHomozygosity(); + } + + public static Genotype GetGenotype(string genotypeString) + { + char separator = GetGenotypeSeparator(genotypeString); + var gtIndexStrings = genotypeString.Split(separator); + var gtIndexes = new int[gtIndexStrings.Length]; + for (var i = 0; i < gtIndexStrings.Length; i++) + gtIndexes[i] = gtIndexStrings[i] == "." ? -1 : int.Parse(gtIndexStrings[i]); + return new Genotype(gtIndexes, separator == '|'); + } + + private bool GetHomozygosity() + { + for (var i = 1; i < AlleleIndexes.Length; i++) + { + if (AlleleIndexes[i] != AlleleIndexes[0]) return false; + } + return true; + } + + private static char GetGenotypeSeparator(string genotypeString) + { + foreach (char c in genotypeString) + { + if (!char.IsDigit(c) && c != '.') return c; + } + // treat haplotype as phased + return '|'; + } + + // used for recomposition purpose + // for simiplicity, 0/1 and 1/0 are considered different, as neither of them would be recomposed + public bool Equals(Genotype other) => AlleleIndexes.SequenceEqual(other.AlleleIndexes) && + (IsPhased == other.IsPhased || IsHomozygous && other.IsHomozygous); + + public override int GetHashCode() + { + unchecked + { + int hashCode = IsPhased ? 1 : 0; + foreach (int genotype in AlleleIndexes) + { + hashCode = (hashCode * 1201) ^ genotype.GetHashCode(); + } + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/Phantom/DataStructures/GenotypeBlock.cs b/Phantom/DataStructures/GenotypeBlock.cs new file mode 100644 index 00000000..a4c2deed --- /dev/null +++ b/Phantom/DataStructures/GenotypeBlock.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Phantom.DataStructures +{ + public sealed class GenotypeBlock : IEquatable + { + public readonly int PosIndex; + public readonly Genotype[] Genotypes; + + public GenotypeBlock(Genotype[] genotypes, int posIndex = 0) + { + Genotypes = genotypes; + PosIndex = posIndex; + } + + public IEnumerable Split(int[] starts, List functionBlockRanges) + { + int numBreaks = Genotypes.Length - 1; + if (numBreaks == 0) return new List(); + int ploidy = Genotypes[0].AlleleIndexes.Length; + var breaks = new bool[ploidy][]; + for (int i = 0; i < ploidy; i++) + { + breaks[i] = GetAlleleBreaks(i, starts, functionBlockRanges); + } + bool[] finalBreaks = GetFinalBreaks(breaks); + return GetGenotypeBlocks(finalBreaks); + } + + private bool[] GetAlleleBreaks(int haplotypeIndex, int[] starts, List functionBlockRanges) + { + int numBreaks = Genotypes.Length - 1; + var alleleBreaks = new bool[numBreaks]; + int lastNonRefPosition = -1; + + // function block checking + for (int gtIndex = 0; gtIndex < Genotypes.Length; gtIndex++) + { + int alleleIndex = Genotypes[gtIndex].AlleleIndexes[haplotypeIndex]; + if (alleleIndex == 0) + { + // check leading ref positions + if (gtIndex == 0 || alleleBreaks[gtIndex - 1] && gtIndex < numBreaks) + { + alleleBreaks[gtIndex] = true; + } + // check tailing ref positions + else if (gtIndex == numBreaks) + { + MakeBreakAndCheckTailingRefPositions(gtIndex - 1, haplotypeIndex, alleleBreaks); + } + } + else + { + if (gtIndex > 0) + { + bool outOfRange = lastNonRefPosition != -1 && starts[PosIndex + gtIndex] > functionBlockRanges[lastNonRefPosition]; + if (outOfRange) + MakeBreakAndCheckTailingRefPositions(gtIndex - 1, haplotypeIndex, alleleBreaks); + } + lastNonRefPosition = PosIndex + gtIndex; + } + } + return alleleBreaks; + } + + private void MakeBreakAndCheckTailingRefPositions(int breakIndex, int haplotypeIndex, bool[] alleleBreaks) + { + alleleBreaks[breakIndex] = true; + // check "tailing" referece positions before this break + for (int i = breakIndex - 1; i >= 0; i--) + { + // stop when the break bool is true or the allele is non-ref + if (alleleBreaks[i] || Genotypes[i + 1].AlleleIndexes[haplotypeIndex] != 0) break; + alleleBreaks[i] = true; + } + } + + private List GetGenotypeBlocks(bool[] finalBreaks) + { + var subGenotypeBlocks = new List(); + int subBlockStart = 0; + int subBlockEnd = 0; + for (int i = 0; i < finalBreaks.Length; i++) + { + if (!finalBreaks[i]) + { + subBlockEnd++; + } + else + { + int subBlockSize = subBlockEnd - subBlockStart + 1; + if (subBlockSize > 1) + { + subGenotypeBlocks.Add(GetSubBlock(subBlockStart, subBlockSize)); + } + subBlockStart = i + 1; + subBlockEnd = i + 1; + } + } + if (subBlockEnd - subBlockStart >= 1) subGenotypeBlocks.Add(GetSubBlock(subBlockStart, subBlockEnd - subBlockStart + 1)); + return subGenotypeBlocks; + } + + public GenotypeBlock GetSubBlock(int subBlockStart, int numPositions) => new GenotypeBlock(new ArraySegment(Genotypes, subBlockStart, numPositions).ToArray(), + PosIndex + subBlockStart); + + private static bool[] GetFinalBreaks(bool[][] breaks) + { + int ploidy = breaks.Length; + int numBreaks = breaks[0].Length; + bool[] finalBreaks = new bool[numBreaks]; + for (int i = 0; i < numBreaks; i++) + { + bool anyFalse = false; + for (int j = 0; j < ploidy; j++) + { + if (breaks[j][i]) continue; + anyFalse = true; + break; + } + if (!anyFalse) finalBreaks[i] = true; + } + return finalBreaks; + } + + public bool Equals(GenotypeBlock other) => Genotypes.SequenceEqual(other.Genotypes) && PosIndex == other.PosIndex; + + public override int GetHashCode() + { + unchecked + { + int hashCode = PosIndex.GetHashCode(); + foreach (var genotype in Genotypes) + { + hashCode = (hashCode * 1201) ^ genotype.GetHashCode(); + } + return hashCode; + } + } + + + } +} \ No newline at end of file diff --git a/Phantom/DataStructures/PositionSet.cs b/Phantom/DataStructures/PositionSet.cs index 441574a6..64b0c0d0 100644 --- a/Phantom/DataStructures/PositionSet.cs +++ b/Phantom/DataStructures/PositionSet.cs @@ -11,17 +11,18 @@ namespace Phantom.DataStructures public sealed class PositionSet : IPositionSet { public List SimplePositions { get; } - public List FunctionBlockRanges { get; } + private List FunctionBlockRanges { get; } private static readonly HashSet SupportedVariantTypes = new HashSet { VariantType.SNV }; - public int NumSamples { get; private set; } + public int NumSamples { get; } public string ChrName { get; } private readonly int _numPositions; public AlleleSet AlleleSet { get; private set; } public Dictionary> AlleleIndexBlockToSampleIndex { get; private set; } - private HashSet[] _allelesWithUnsupportedTypes; + private HashSet[] _allelesWithUnsupportedTypes; private string[,][] _sampleInfo; - public string[][] GqInfo; - public string[][] PsInfo; + public TagInfo GqInfo; + public TagInfo PsInfo; + public TagInfo GtInfo; internal PositionSet(List simpleSimplePositions, List functionBlockRanges) { @@ -38,32 +39,15 @@ public static PositionSet CreatePositionSet(List simpleSimplePo positionSet.AlleleSet = GenerateAlleleSet(positionSet); positionSet._allelesWithUnsupportedTypes = GetAllelesWithUnsupportedTypes(positionSet); positionSet._sampleInfo = GetSampleInfo(positionSet); - var phaseSetAndGqIndexes = positionSet.GetSampleTagIndexes(new[] { "PS", "GQ" }); - positionSet.PsInfo = GetTagInfo(positionSet._sampleInfo, phaseSetAndGqIndexes[0],ExtractSamplePhaseSet); - positionSet.GqInfo = GetTagInfo(positionSet._sampleInfo, phaseSetAndGqIndexes[1],ExtractSampleGq); + var phaseSetAndGqIndexes = positionSet.GetSampleTagIndexes(new[] { "GT", "PS", "GQ" }); + positionSet.GtInfo = TagInfo.GetTagInfo(positionSet._sampleInfo, phaseSetAndGqIndexes[0], ExtractSampleValue, Genotype.GetGenotype); + positionSet.PsInfo = TagInfo.GetTagInfo(positionSet._sampleInfo, phaseSetAndGqIndexes[1], ExtractSampleValue, x => x); + positionSet.GqInfo = TagInfo.GetTagInfo(positionSet._sampleInfo, phaseSetAndGqIndexes[2], ExtractSampleValue, x => x); var genotypeToSampleIndex = GetGenotypeToSampleIndex(positionSet); positionSet.AlleleIndexBlockToSampleIndex = AlleleIndexBlock.GetAlleleIndexBlockToSampleIndex(genotypeToSampleIndex, positionSet._allelesWithUnsupportedTypes, positionSet.AlleleSet.Starts, positionSet.FunctionBlockRanges); return positionSet; } - - private static string[][] GetTagInfo(string[,][] positionSetSampleInfo, int[] tagIndexes, Func tagExtractionMethod) - { - int numPositions = positionSetSampleInfo.GetLength(0); - int numSamples = positionSetSampleInfo.GetLength(1); - if (numPositions != tagIndexes.Length) throw new InvalidDataException($"The inconsistent numbers of positions: {numPositions} in sample info array, {tagIndexes.Length} in GQ index array"); - var tagInfo = new String[numSamples][]; - for (int sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) - { - tagInfo[sampleIndex] = new string[numPositions]; - for (var i = 0; i < numPositions; i++) - { - tagInfo[sampleIndex][i] = tagExtractionMethod(tagIndexes[i], positionSetSampleInfo[i, sampleIndex]); - } - } - return tagInfo; - } - private static string[,][] GetSampleInfo(PositionSet positionSet) { var sampleInfo = new String[positionSet._numPositions, positionSet.NumSamples][]; @@ -120,22 +104,14 @@ internal int[][] GetSampleTagIndexes(string[] tagsToExtract) return indexes; } - internal static string ExtractSamplePhaseSet(int phaseSetTagIndex, string[] sampleInfo) - { - if (phaseSetTagIndex == -1 || sampleInfo.Length <= phaseSetTagIndex) return "."; - if (sampleInfo.Length == 1 && sampleInfo[0] == ".") return "."; - var phaseSet = sampleInfo[phaseSetTagIndex]; - return phaseSet; - } - - internal static string ExtractSampleGq(int gqTagIndex, string[] sampleInfo) => gqTagIndex == -1 || sampleInfo.Length <= gqTagIndex ? "." : sampleInfo[gqTagIndex]; + internal static string ExtractSampleValue(int tagIndex, string[] sampleInfo) => tagIndex == -1 || sampleInfo.Length <= tagIndex ? "." : sampleInfo[tagIndex]; - private static Dictionary<(string Genotypes, int Start), List> GetGenotypeToSampleIndex(PositionSet positionSet) + private static Dictionary> GetGenotypeToSampleIndex(PositionSet positionSet) { - var genotypeToSample = new Dictionary<(string, int), List>(); + var genotypeToSample = new Dictionary>(); for (int sampleIndex = 0; sampleIndex < positionSet.NumSamples; sampleIndex++) { - var genotypesAndStartIndexes = GetGenotypesAndStarts(positionSet, sampleIndex); + var genotypesAndStartIndexes = GetGenotypeBlocks(positionSet, sampleIndex); foreach (var genotypeAndSartIndex in genotypesAndStartIndexes) { if (genotypeToSample.ContainsKey(genotypeAndSartIndex)) genotypeToSample[genotypeAndSartIndex].Add(sampleIndex); @@ -145,51 +121,64 @@ internal static string ExtractSamplePhaseSet(int phaseSetTagIndex, string[] samp return genotypeToSample; } - private static List<(string, int)> GetGenotypesAndStarts(PositionSet positionSet, int sampleIndex) + + // GenotypeBlocks can be shared by multiple samples + // We mainly utilize phase set informaiton at this step to avoid duplicated calculation + // These GenotypeBlocks could be further segmented when more details considered + private static IEnumerable GetGenotypeBlocks(PositionSet positionSet, int sampleIndex) { - var genotypesAndStartIndexes = new List<(string, int)>(); - var gtTags = new List(); - int startIndexInBlock = 0; - string previousPhaseSetId = null; - for (var i = 0; i < positionSet._numPositions; i++) + var genotypes = positionSet.GtInfo.Values[sampleIndex]; + var entireBlock = new GenotypeBlock(genotypes); + var blockRanges = GetGenotypeBlockRange(positionSet.PsInfo.Values[sampleIndex], genotypes.Select(x => x.IsHomozygous).ToArray()); + var genotypeBlocks = new LinkedList(); + foreach (var range in blockRanges) + genotypeBlocks.AddLast(entireBlock.GetSubBlock(range.StartIndex, range.PositionCount)); + + return genotypeBlocks; + } + + // Just do a minimal process here as only phase set informatin is sample specific + // Non sample specific information (i.e. genotype) can be shared by multiple samples and will further processed + private static IEnumerable<(int StartIndex, int PositionCount)> GetGenotypeBlockRange(string[] phaseSetIds, + bool[] isHomomzygous) + { + var blocks = new LinkedList<(int, int)>(); + int numPositions = phaseSetIds.Length; + int startCurrentHomoBlock = -1; + int startCurrentBlock = 0; + string previousPhaseSetId = phaseSetIds[0]; + for (var i = 1; i < numPositions; i++) { - var thisSampleInfo = positionSet._sampleInfo[i, sampleIndex]; - string currentPhaseSetId = positionSet.PsInfo[sampleIndex][i]; - if (previousPhaseSetId == null) - { - // ReSharper disable once RedundantAssignment - previousPhaseSetId = currentPhaseSetId; - } - else if (currentPhaseSetId != previousPhaseSetId) + bool inPreviousPhaseSet = phaseSetIds[i] == previousPhaseSetId; + if (!isHomomzygous[i]) { - if (gtTags.Count > 1) + if (!inPreviousPhaseSet) { - genotypesAndStartIndexes.Add((string.Join(";", gtTags), startIndexInBlock)); + blocks.AddLast((startCurrentBlock, i - startCurrentBlock + 1)); + startCurrentBlock = startCurrentHomoBlock == -1 ? i : startCurrentHomoBlock; } - gtTags = new List(); - startIndexInBlock = i; + startCurrentHomoBlock = -1; + } + else if (inPreviousPhaseSet) + { + startCurrentHomoBlock = i; } - gtTags.Add(thisSampleInfo[0]); - previousPhaseSetId = currentPhaseSetId; - } - if (gtTags.Count > 1) - { - genotypesAndStartIndexes.Add((string.Join(";", gtTags), startIndexInBlock)); } - return genotypesAndStartIndexes; + blocks.AddLast((startCurrentBlock, numPositions - startCurrentBlock)); + return blocks; } - private static HashSet[] GetAllelesWithUnsupportedTypes(PositionSet positionSet) + private static HashSet[] GetAllelesWithUnsupportedTypes(PositionSet positionSet) { - var allelesWithUnsupportedTypes = new HashSet[positionSet._numPositions]; + var allelesWithUnsupportedTypes = new HashSet[positionSet._numPositions]; for (int posIndex = 0; posIndex < positionSet._numPositions; posIndex++) { - allelesWithUnsupportedTypes[posIndex] = new HashSet(); + allelesWithUnsupportedTypes[posIndex] = new HashSet(); var thisPosition = positionSet.SimplePositions[posIndex]; for (int varIndex = 0; varIndex < thisPosition.AltAlleles.Length; varIndex++) { - if (!(IsSupportedVariantType(thisPosition.RefAllele, thisPosition.AltAlleles[varIndex]) || thisPosition.VcfFields[VcfCommon.AltIndex] == VcfCommon.GatkNonRefAllele)) //todo: simplify the logic - allelesWithUnsupportedTypes[posIndex].Add((varIndex + 1).ToString()); // GT tag is 1-based + if (!(IsSupportedVariantType(thisPosition.RefAllele, thisPosition.AltAlleles[varIndex]) || thisPosition.VcfFields[VcfCommon.AltIndex] == VcfCommon.GatkNonRefAllele)) + allelesWithUnsupportedTypes[posIndex].Add(varIndex + 1); // GT tag is 1-based } } return allelesWithUnsupportedTypes; @@ -199,6 +188,33 @@ private static bool IsSupportedVariantType(string refAllele, string altAllele) { return !(altAllele.StartsWith("<") || altAllele == "*") && SupportedVariantTypes.Contains(Vcf.VariantCreator.SmallVariantCreator.GetVariantType(refAllele, altAllele)); } + } + + public sealed class TagInfo + { + public readonly T[][] Values; + + private TagInfo(T[][] values) + { + Values = values; + } + + public static TagInfo GetTagInfo(string[,][] positionSetSampleInfo, int[] tagIndexes, Func tagExtractionMethod, Func tagProcessingMethod) + { + int numPositions = positionSetSampleInfo.GetLength(0); + int numSamples = positionSetSampleInfo.GetLength(1); + if (numPositions != tagIndexes.Length) throw new InvalidDataException($"The inconsistent numbers of positions: {numPositions} in sample info array, {tagIndexes.Length} in GQ index array"); + var tagInfo = new T[numSamples][]; + for (int sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) + { + tagInfo[sampleIndex] = new T[numPositions]; + for (var i = 0; i < numPositions; i++) + { + tagInfo[sampleIndex][i] = tagProcessingMethod(tagExtractionMethod(tagIndexes[i], positionSetSampleInfo[i, sampleIndex])); + } + } + return new TagInfo(tagInfo); + } } } \ No newline at end of file diff --git a/Phantom/Workers/RecomposedAlleleSet.cs b/Phantom/Workers/RecomposedAlleleSet.cs new file mode 100644 index 00000000..b880b9f2 --- /dev/null +++ b/Phantom/Workers/RecomposedAlleleSet.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Phantom.Workers +{ + public sealed class RecomposedAlleleSet + { + public readonly Dictionary RecomposedAlleles; + private readonly int _numSamples; + private readonly string _chrName; + private const string VariantId = "."; + private const string InfoTag = "RECOMPOSED"; + + + public RecomposedAlleleSet(string chrName, int numSamples) + { + _numSamples = numSamples; + _chrName = chrName; + RecomposedAlleles = new Dictionary(); + } + + public List GetRecomposedVcfRecords() + { + var vcfRecords = new List(); + foreach (var variantSite in RecomposedAlleles.Keys.OrderBy(x => x)) + { + var varInfo = RecomposedAlleles[variantSite]; + var altAlleleList = new List(); + int genotypeIndex = 1; // genotype index of alt allele + var sampleGenotypes = new List[_numSamples]; + for (int i = 0; i < _numSamples; i++) sampleGenotypes[i] = new List(); + foreach (var altAllele in varInfo.AltAlleleToSample.Keys.OrderBy(x => x)) + { + var sampleAlleles = varInfo.AltAlleleToSample[altAllele]; + int currentGenotypeIndex; + if (altAllele == variantSite.RefAllele) + { + currentGenotypeIndex = 0; + } + else + { + currentGenotypeIndex = genotypeIndex; + genotypeIndex++; + altAlleleList.Add(altAllele); + } + foreach (var sampleAllele in sampleAlleles) + { + SetGenotypeWithAlleleIndex(sampleGenotypes[sampleAllele.SampleIndex], sampleAllele.AlleleIndex, + currentGenotypeIndex); + } + } + string altAlleleColumn = string.Join(",", altAlleleList); + vcfRecords.Add(GetVcfFields(variantSite, altAlleleColumn, varInfo.Qual, varInfo.Filter, sampleGenotypes, varInfo.SampleGqs, varInfo.SamplePhaseSets)); + } + + return vcfRecords; + } + + private static void SetGenotypeWithAlleleIndex(List sampleGenotype, byte sampleAlleleAlleleIndex, int currentGenotypeIndex) + { + if (sampleGenotype.Count == sampleAlleleAlleleIndex) + { + sampleGenotype.Add(currentGenotypeIndex); + return; + } + + if (sampleGenotype.Count < sampleAlleleAlleleIndex) + { + int extraSpace = sampleAlleleAlleleIndex - sampleGenotype.Count + 1; + sampleGenotype.AddRange(Enumerable.Repeat(-1, extraSpace)); + } + sampleGenotype[sampleAlleleAlleleIndex] = currentGenotypeIndex; + } + + private string[] GetVcfFields(VariantSite varSite, string altAlleleColumn, string qual, string filter, List[] sampleGenoTypes, string[] sampleGqs, string[] samplePhasesets, string variantId = VariantId, string info = InfoTag) + { + var vcfFields = new List + { + _chrName, + varSite.Start.ToString(), + variantId, + varSite.RefAllele, + altAlleleColumn, + qual, + filter, + info + }; + + AddFormatAndSampleColumns(sampleGenoTypes, sampleGqs, samplePhasesets, ref vcfFields); + return vcfFields.ToArray(); + } + + private static void AddFormatAndSampleColumns(List[] sampleGenoTypes, string[] sampleGqs, string[] samplePhasesets, ref List vcfFields) + { + var formatTags = "GT"; + var hasGq = false; + var hasPs = false; + int numSamples = sampleGenoTypes.Length; + + var sampleGenotypeStrings = new string[numSamples]; + for (var index = 0; index < numSamples; index++) + { + sampleGenotypeStrings[index] = GetGenotype(sampleGenoTypes[index]); + if (sampleGenotypeStrings[index] == ".") continue; + if (sampleGqs[index] != ".") hasGq = true; + if (samplePhasesets[index] != ".") hasPs = true; + if (hasGq && hasPs) break; + } + + int numFields = 1; + + if (hasGq) + { + formatTags += ":GQ"; + numFields++; + } + if (hasPs) + { + formatTags += ":PS"; + numFields++; + } + + vcfFields.Add(formatTags); + + for (var index = 0; index < numSamples; index++) + { + var sampleGenotypeStr = sampleGenotypeStrings[index]; + if (sampleGenotypeStr == ".") vcfFields.Add("."); + else + { + var nonMissingFields = new string[numFields]; + nonMissingFields[0] = sampleGenotypeStr; + var fieldIndex = 1; + if (hasGq) + { + nonMissingFields[fieldIndex] = sampleGqs[index]; + fieldIndex++; + } + if (hasPs) + { + nonMissingFields[fieldIndex] = samplePhasesets[index]; + } + + var sampleColumnStr = string.Join(":", TrimTrailingMissValues(nonMissingFields)); + vcfFields.Add(sampleColumnStr); + } + } + } + + private static string[] TrimTrailingMissValues(string[] values) + { + int indexLastRemainedValue = values.Length - 1; + // Need to have at least one value remained + for (; indexLastRemainedValue > 0; indexLastRemainedValue--) + { + if (values[indexLastRemainedValue] != ".") break; + } + return new ArraySegment(values, 0, indexLastRemainedValue + 1).ToArray(); + } + + private static string GetGenotype(List sampleGenotype) => sampleGenotype.Count == 0 ? "." : string.Join("|", sampleGenotype); + } +} \ No newline at end of file diff --git a/Phantom/Workers/VariantGenerator.cs b/Phantom/Workers/VariantGenerator.cs index c31ea44a..7e8901a1 100644 --- a/Phantom/Workers/VariantGenerator.cs +++ b/Phantom/Workers/VariantGenerator.cs @@ -4,7 +4,6 @@ using ErrorHandling.Exceptions; using Phantom.DataStructures; using Phantom.Interfaces; -using Phantom.Workers; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Positions; using VariantAnnotation.Interface.Providers; @@ -62,7 +61,7 @@ private static VariantInfo GetVariantInfo(PositionSet positionSet, AlleleIndexBl string filter = "PASS"; var positions = positionSet.SimplePositions; var startIndex = alleleIndexBlock.PositionIndex; - var numPositions = alleleIndexBlock.AlleleIndexes.Count; + var numPositions = alleleIndexBlock.AlleleIndexes.Length; var numSamples = positionSet.NumSamples; string[] quals = new string[numPositions]; @@ -77,12 +76,16 @@ private static VariantInfo GetVariantInfo(PositionSet positionSet, AlleleIndexBl string[] gqValues = new string[numSamples]; for (int i = 0; i < numSamples; i++) - gqValues[i] = GetStringWithMinValueOrDot(new ArraySegment(positionSet.GqInfo[i], startIndex, numPositions).ToArray()); + gqValues[i] = GetStringWithMinValueOrDot(new ArraySegment(positionSet.GqInfo.Values[i], startIndex, numPositions).ToArray()); string[] psValues = new string[numSamples]; for (int i = 0; i < numSamples; i++) - // PS tags are the same in the decomposed variants - psValues[i] = positionSet.PsInfo[i][startIndex]; + { + var psTagsThisSample = + new ArraySegment(positionSet.PsInfo.Values[i], startIndex, numPositions); + var isHomozygous = new ArraySegment(positionSet.GtInfo.Values[i].Select(x => x.IsHomozygous).ToArray(), startIndex, numPositions); + psValues[i] = GetPhaseSetForRecomposedVarint(psTagsThisSample, isHomozygous); + } return new VariantInfo(qual, filter, gqValues, psValues); } @@ -104,9 +107,18 @@ private static string GetStringWithMinValueOrDot(string[] strings) return currentString; } + private static string GetPhaseSetForRecomposedVarint(IEnumerable psTagsThisSample, IEnumerable isHomozygous) + { + foreach (var (psTag, homozygousity) in psTagsThisSample.Zip(isHomozygous, (a, b) => (a, b))) + { + if (!homozygousity) return psTag; + } + return "."; + } + internal static (int Start, int End, string Ref, string Alt) GetPositionsAndRefAltAlleles(AlleleIndexBlock alleleIndexBlock, AlleleSet alleleSet, string totalRefSequence, int regionStart, HashSet<(int, int)> decomposedPosVarIndex) { - int numPositions = alleleIndexBlock.AlleleIndexes.Count; + int numPositions = alleleIndexBlock.AlleleIndexes.Length; int firstPositionIndex = alleleIndexBlock.PositionIndex; int lastPositionIndex = alleleIndexBlock.PositionIndex + numPositions - 1; @@ -184,163 +196,4 @@ public void AddAllele(string altAllele, List sampleAlleles) AltAlleleToSample.Add(altAllele, sampleAlleles); } } - -} - -internal sealed class RecomposedAlleleSet -{ - public Dictionary RecomposedAlleles; - private readonly int _numSamples; - private readonly string _chrName; - private const string VariantId = "."; - private const string InfoTag = "RECOMPOSED"; - - - public RecomposedAlleleSet(string chrName, int numSamples) - { - _numSamples = numSamples; - _chrName = chrName; - RecomposedAlleles = new Dictionary(); - } - - public List GetRecomposedVcfRecords() - { - var vcfRecords = new List(); - foreach (var variantSite in RecomposedAlleles.Keys.OrderBy(x => x)) - { - var varInfo = RecomposedAlleles[variantSite]; - var altAlleleList = new List(); - int genotypeIndex = 1; // genotype index of alt allele - var sampleGenotypes = new List[_numSamples]; - for (int i = 0; i < _numSamples; i++) sampleGenotypes[i] = new List(); - foreach (var altAllele in varInfo.AltAlleleToSample.Keys.OrderBy(x => x)) - { - var sampleAlleles = varInfo.AltAlleleToSample[altAllele]; - int currentGenotypeIndex; - if (altAllele == variantSite.RefAllele) - { - currentGenotypeIndex = 0; - } - else - { - currentGenotypeIndex = genotypeIndex; - genotypeIndex++; - altAlleleList.Add(altAllele); - } - foreach (var sampleAllele in sampleAlleles) - { - SetGenotypeWithAlleleIndex(sampleGenotypes[sampleAllele.SampleIndex], sampleAllele.AlleleIndex, - currentGenotypeIndex); - } - } - var altAlleleColumn = string.Join(",", altAlleleList); - vcfRecords.Add(GetVcfFields(variantSite, altAlleleColumn, varInfo.Qual, varInfo.Filter, sampleGenotypes, varInfo.SampleGqs, varInfo.SamplePhaseSets)); - } - - return vcfRecords; - } - - private void SetGenotypeWithAlleleIndex(List sampleGenotype, byte sampleAlleleAlleleIndex, int currentGenotypeIndex) - { - if (sampleGenotype.Count == sampleAlleleAlleleIndex) - { - sampleGenotype.Add(currentGenotypeIndex); - return; - } - - if (sampleGenotype.Count < sampleAlleleAlleleIndex) - { - int extraSpace = sampleAlleleAlleleIndex - sampleGenotype.Count + 1; - sampleGenotype.AddRange(Enumerable.Repeat(-1, extraSpace)); - } - sampleGenotype[sampleAlleleAlleleIndex] = currentGenotypeIndex; - } - - private string[] GetVcfFields(VariantSite varSite, string altAlleleColumn, string qual, string filter, List[] sampleGenoTypes, string[] sampleGqs, string[] samplePhasesets, string variantId = VariantId, string info = InfoTag) - { - var vcfFields = new List - { - _chrName, - varSite.Start.ToString(), - variantId, - varSite.RefAllele, - altAlleleColumn, - qual, - filter, - info - }; - - AddFormatAndSampleColumns(sampleGenoTypes, sampleGqs, samplePhasesets, ref vcfFields); - return vcfFields.ToArray(); - } - - private static void AddFormatAndSampleColumns(List[] sampleGenoTypes, string[] sampleGqs, string[] samplePhasesets, ref List vcfFields) - { - var formatTags = "GT"; - var hasGq = false; - var hasPs = false; - int numSamples = sampleGenoTypes.Length; - - var sampleGenotypeStrings = new string[numSamples]; - for (var index = 0; index < numSamples; index++) - { - sampleGenotypeStrings[index] = GetGenotype(sampleGenoTypes[index]); - if (sampleGenotypeStrings[index] == ".") continue; - if (sampleGqs[index] != ".") hasGq = true; - if (samplePhasesets[index] != ".") hasPs = true; - if (hasGq && hasPs) break; - } - - int numFields = 1; - - if (hasGq) - { - formatTags += ":GQ"; - numFields++; - } - if (hasPs) - { - formatTags += ":PS"; - numFields++; - } - - vcfFields.Add(formatTags); - - for (var index = 0; index < numSamples; index++) - { - var sampleGenotypeStr = sampleGenotypeStrings[index]; - if (sampleGenotypeStr == ".") vcfFields.Add("."); - else - { - var nonMissingFields = new string[numFields]; - nonMissingFields[0] = sampleGenotypeStr; - var fieldIndex = 1; - if (hasGq) - { - nonMissingFields[fieldIndex] = sampleGqs[index]; - fieldIndex++; - } - if (hasPs) - { - nonMissingFields[fieldIndex] = samplePhasesets[index]; - } - - var sampleColumnStr = string.Join(":", TrimTrailingMissValues(nonMissingFields)); - vcfFields.Add(sampleColumnStr); - } - } - } - - private static string[] TrimTrailingMissValues(string[] values) - { - int indexLastRemainedValue = values.Length - 1; - // Need to have at least one value remained - for (; indexLastRemainedValue > 0; indexLastRemainedValue--) - { - if (values[indexLastRemainedValue] != ".") break; - } - return new ArraySegment(values, 0, indexLastRemainedValue + 1).ToArray(); - } - - private static string GetGenotype(List sampleGenotype) => sampleGenotype.Count == 0 ? "." : string.Join("|", sampleGenotype); } \ No newline at end of file diff --git a/UnitTests/Phantom/DataStructures/AlleleIndexBlockTests.cs b/UnitTests/Phantom/DataStructures/AlleleIndexBlockTests.cs index 573f5283..e7537d75 100644 --- a/UnitTests/Phantom/DataStructures/AlleleIndexBlockTests.cs +++ b/UnitTests/Phantom/DataStructures/AlleleIndexBlockTests.cs @@ -11,7 +11,7 @@ public sealed class AlleleIndexBlockTests [Fact] public void GetPloidyFromGenotypes_DotIsIgnored() { - string genotypes = ".;1|2;0/2"; + var genotypes = new[] { Genotype.GetGenotype("."), Genotype.GetGenotype("1|2"), Genotype.GetGenotype("0/2") }; var ploidy = AlleleIndexBlock.GetMaxPloidy(genotypes); Assert.Equal(2, ploidy); } @@ -39,67 +39,67 @@ public void TrimTaillingRefAlleles_RefIsTrimmed() [Fact] public void GetAlleleIndexBlockToSampleIndex_AsExpected() { - var genotypes1 = new[] {"1|2", "1/1", "0|1", "./1"}; - var genotypes2 = new[] {"0/1", "0|0", "1|1", "1|1"}; - var genotypes3 = new[] {"0|1", "1|1", "0/0"}; + var genotypeBlock1 = new GenotypeBlock(new[] { "1|2", "1/1", "0|1", "./1" }.Select(Genotype.GetGenotype).ToArray()); + var genotypeBlock2 = new GenotypeBlock(new[] { "0/1", "0|0", "1|1", "1|1" }.Select(Genotype.GetGenotype).ToArray()); + var genotypeBlock3 = new GenotypeBlock(new[] { "0|1", "1|1", "0/0" }.Select(Genotype.GetGenotype).ToArray(), 1); var genotypeToSample = - new Dictionary<(string, int), List> + new Dictionary> { - {(string.Join(";", genotypes1), 0), new List {0, 1}}, - {(string.Join(";", genotypes2), 0), new List {2}}, - {(string.Join(";", genotypes3), 1), new List {3}} + {genotypeBlock1, new List {0, 1}}, + {genotypeBlock2, new List {2}}, + {genotypeBlock3, new List {3}} }; - var indexOfUnsupportedVars = Enumerable.Repeat(new HashSet(), genotypes1.Length).ToArray(); + var indexOfUnsupportedVars = Enumerable.Repeat(new HashSet(), 4).ToArray(); var starts = Enumerable.Range(100, 4).ToArray(); var functionBlockRanges = starts.Select(x => x + 2).ToList(); var alleleIndexBlocksToSample = AlleleIndexBlock.GetAlleleIndexBlockToSampleIndex(genotypeToSample, indexOfUnsupportedVars, starts, functionBlockRanges); - var expectedBlock1 = new AlleleIndexBlock(0, new List {1, 1, 0}); - var expectedBlock2 = new AlleleIndexBlock(0, new List {2, 1, 1}); - var expectedBlock3 = new AlleleIndexBlock(2, new List {1, 1}); - var expectedBlock4 = new AlleleIndexBlock(1, new List {1, 1}); - var expectedBlock5 = new AlleleIndexBlock(1, new List {0, 1}); + var expectedBlock1 = new AlleleIndexBlock(0, new [] { 1, 1, 0 }); + var expectedBlock2 = new AlleleIndexBlock(0, new [] { 2, 1, 1 }); + var expectedBlock3 = new AlleleIndexBlock(2, new [] { 1, 1 }); + var expectedBlock4 = new AlleleIndexBlock(1, new [] { 1, 1 }); + var expectedBlock5 = new AlleleIndexBlock(1, new [] { 0, 1 }); Assert.True(alleleIndexBlocksToSample.ContainsKey(expectedBlock1)); Assert.True(alleleIndexBlocksToSample[expectedBlock1] - .SequenceEqual(new[] {new SampleAllele(0, 0), new SampleAllele(1, 0)})); + .SequenceEqual(new[] { new SampleAllele(0, 0), new SampleAllele(1, 0) })); Assert.True(alleleIndexBlocksToSample.ContainsKey(expectedBlock2)); Assert.True(alleleIndexBlocksToSample[expectedBlock2] - .SequenceEqual(new[] {new SampleAllele(0, 1), new SampleAllele(1, 1)})); + .SequenceEqual(new[] { new SampleAllele(0, 1), new SampleAllele(1, 1) })); Assert.True(alleleIndexBlocksToSample.ContainsKey(expectedBlock3)); Assert.True(alleleIndexBlocksToSample[expectedBlock3] - .SequenceEqual(new[] {new SampleAllele(2, 0), new SampleAllele(2, 1)})); + .SequenceEqual(new[] { new SampleAllele(2, 0), new SampleAllele(2, 1) })); Assert.True(alleleIndexBlocksToSample.ContainsKey(expectedBlock4)); - Assert.True(alleleIndexBlocksToSample[expectedBlock4].SequenceEqual(new[] {new SampleAllele(3, 1)})); + Assert.True(alleleIndexBlocksToSample[expectedBlock4].SequenceEqual(new[] { new SampleAllele(3, 1) })); Assert.True(alleleIndexBlocksToSample.ContainsKey(expectedBlock5)); - Assert.True(alleleIndexBlocksToSample[expectedBlock5].SequenceEqual(new[] {new SampleAllele(3, 0)})); + Assert.True(alleleIndexBlocksToSample[expectedBlock5].SequenceEqual(new[] { new SampleAllele(3, 0) })); } [Fact] public void GetAlleleIndexBlockToSampleIndex_AlleleBlock_WithInternalRefPositions_SplitIfOutOfRange() { - var genotypes1 = new[] {"1|2", "0/0", "0|0", "1/1"}; - var genotypes2 = new[] {"1/1", "0|0", "1|1"}; - var genotypes3 = new[] {"1|2", "0|0", "1|1"}; - var starts = new[] {100, 102, 103, 104}; - var functionBlockRanges = starts.Select(x => x + 2).ToList(); - + var genotypeBlock1 = new GenotypeBlock(new[] { "1|2", "0/0", "0|0", "1/1" }.Select(Genotype.GetGenotype).ToArray()); + var genotypeBlock2 = new GenotypeBlock(new[] { "1/1", "0|0", "1|1" }.Select(Genotype.GetGenotype).ToArray()); + var genotypeBlock3 = new GenotypeBlock(new[] { "1|2", "0|0", "1|1" }.Select(Genotype.GetGenotype).ToArray(), 1); var genotypeToSample = - new Dictionary<(string, int), List> + new Dictionary> { - {(string.Join(";", genotypes1), 0), new List {0}}, - {(string.Join(";", genotypes2), 0), new List {1}}, - {(string.Join(";", genotypes3), 1), new List {2}} + {genotypeBlock1, new List {0}}, + {genotypeBlock2, new List {1}}, + {genotypeBlock3, new List {2}} }; - var indexOfUnsupportedVars = Enumerable.Repeat(new HashSet(), genotypes1.Length).ToArray(); + var indexOfUnsupportedVars = Enumerable.Repeat(new HashSet(), genotypeBlock1.Genotypes.Length).ToArray(); + + var starts = new[] { 100, 102, 103, 104 }; + var functionBlockRanges = starts.Select(x => x + 2).ToList(); var alleleIndexBlocksToSample = AlleleIndexBlock.GetAlleleIndexBlockToSampleIndex(genotypeToSample, indexOfUnsupportedVars, starts, functionBlockRanges); - var expectedBlock1 = new AlleleIndexBlock(1, new List {1, 0, 1}); - var expectedBlock2 = new AlleleIndexBlock(1, new List {2, 0, 1}); + var expectedBlock1 = new AlleleIndexBlock(1, new [] { 1, 0, 1 }); + var expectedBlock2 = new AlleleIndexBlock(1, new [] { 2, 0, 1 }); Assert.True(alleleIndexBlocksToSample.ContainsKey(expectedBlock1)); Assert.True(alleleIndexBlocksToSample[expectedBlock1].SequenceEqual(new[] { new SampleAllele(2, 0) })); @@ -109,31 +109,32 @@ public void GetAlleleIndexBlockToSampleIndex_AlleleBlock_WithInternalRefPosition [Fact] public void GetAlleleIndexBlockToSampleIndex_AlleleBlock_OneAlleleIsRef_EachTime() - { - var genotypes1 = new[] { "1|0", "0|1", "1|0", "0|1" }; - var genotypes2 = new[] { "1/1", "0|1", "1|0" }; - var genotypes3 = new[] { "0|0", "1|0", "0|1", "0|0" }; - var genotypes4 = new[] { "0|1", "1|0", "1|0"}; - var starts = new[] { 100, 101, 102, 104 }; - var functionBlockRanges = starts.Select(x => x + 2).ToList(); + { + var genotypeBlock1 = new GenotypeBlock(new[] { "1|0", "0|1", "1|0", "0|1" }.Select(Genotype.GetGenotype).ToArray()); + var genotypeBlock2 = new GenotypeBlock(new[] { "1/1", "0|1", "1|0" }.Select(Genotype.GetGenotype).ToArray(), 1); + var genotypeBlock3 = new GenotypeBlock(new[] { "0|0", "1|0", "0|1", "0|0" }.Select(Genotype.GetGenotype).ToArray()); + var genotypeBlock4 = new GenotypeBlock(new[] { "0|1", "1|0", "1|0" }.Select(Genotype.GetGenotype).ToArray()); var genotypeToSample = - new Dictionary<(string, int), List> + new Dictionary> { - {(string.Join(";", genotypes1), 0), new List {0}}, - {(string.Join(";", genotypes2), 1), new List {1}}, - {(string.Join(";", genotypes3), 0), new List {2}}, - {(string.Join(";", genotypes4), 0), new List {3}} + {genotypeBlock1, new List {0}}, + {genotypeBlock2, new List {1}}, + {genotypeBlock3, new List {2}}, + {genotypeBlock4, new List {3}} }; - var indexOfUnsupportedVars = Enumerable.Repeat(new HashSet(), genotypes1.Length).ToArray(); + var indexOfUnsupportedVars = Enumerable.Repeat(new HashSet(), genotypeBlock1.Genotypes.Length).ToArray(); + + var starts = new[] { 100, 101, 102, 104 }; + var functionBlockRanges = starts.Select(x => x + 2).ToList(); var alleleIndexBlocksToSample = AlleleIndexBlock.GetAlleleIndexBlockToSampleIndex(genotypeToSample, indexOfUnsupportedVars, starts, functionBlockRanges); - var expectedBlock1 = new AlleleIndexBlock(0, new List { 1, 0, 1 }); - var expectedBlock2 = new AlleleIndexBlock(0, new List { 0, 1, 0 }); - var expectedBlock3 = new AlleleIndexBlock(1, new List { 1, 0 }); - var expectedBlock4 = new AlleleIndexBlock(1, new List { 1, 1 }); - var expectedBlock5 = new AlleleIndexBlock(1, new List { 0, 0 }); + var expectedBlock1 = new AlleleIndexBlock(0, new [] { 1, 0, 1 }); + var expectedBlock2 = new AlleleIndexBlock(0, new [] { 0, 1, 0 }); + var expectedBlock3 = new AlleleIndexBlock(1, new [] { 1, 0 }); + var expectedBlock4 = new AlleleIndexBlock(1, new [] { 1, 1 }); + var expectedBlock5 = new AlleleIndexBlock(1, new [] { 0, 0 }); Assert.True(alleleIndexBlocksToSample.ContainsKey(expectedBlock1)); Assert.True(alleleIndexBlocksToSample[expectedBlock1].SequenceEqual(new[] { new SampleAllele(0, 0) })); @@ -142,7 +143,7 @@ public void GetAlleleIndexBlockToSampleIndex_AlleleBlock_OneAlleleIsRef_EachTime Assert.True(alleleIndexBlocksToSample.ContainsKey(expectedBlock3)); Assert.True(alleleIndexBlocksToSample[expectedBlock3].SequenceEqual(new[] { new SampleAllele(1, 0) })); Assert.True(alleleIndexBlocksToSample.ContainsKey(expectedBlock4)); - Assert.True(alleleIndexBlocksToSample[expectedBlock4].SequenceEqual(new[] { new SampleAllele(1, 1) , new SampleAllele(3, 0) })); + Assert.True(alleleIndexBlocksToSample[expectedBlock4].SequenceEqual(new[] { new SampleAllele(1, 1), new SampleAllele(3, 0) })); Assert.True(alleleIndexBlocksToSample.ContainsKey(expectedBlock5)); Assert.True(alleleIndexBlocksToSample[expectedBlock5].SequenceEqual(new[] { new SampleAllele(3, 1) })); } diff --git a/UnitTests/Phantom/DataStructures/PositionSetTests.cs b/UnitTests/Phantom/DataStructures/PositionSetTests.cs index 2a657fb8..88130a5b 100644 --- a/UnitTests/Phantom/DataStructures/PositionSetTests.cs +++ b/UnitTests/Phantom/DataStructures/PositionSetTests.cs @@ -43,19 +43,19 @@ public void GetPhaseSetTagIndexes_Return_Correct_PSIndex() [Fact] public void ExtractSamplePhaseSet_NegativePSIndex_ReturnDot() { - Assert.Equal(".", PositionSet.ExtractSamplePhaseSet(-1, new[] { "0|1", "." })); + Assert.Equal(".", PositionSet.ExtractSampleValue(-1, new[] { "0|1", "." })); } [Fact] public void ExtractSamplePhaseSet_PSIsDot_ReturnDot() { - Assert.Equal(".", PositionSet.ExtractSamplePhaseSet(1, new[] { "0|1", "." })); + Assert.Equal(".", PositionSet.ExtractSampleValue(1, new[] { "0|1", "." })); } [Fact] public void ExtractSamplePhaseSet_PSIsNum_ReturnTheNum() { - Assert.Equal("123", PositionSet.ExtractSamplePhaseSet(1, new[] { "0|1", "123" })); + Assert.Equal("123", PositionSet.ExtractSampleValue(1, new[] { "0|1", "123" })); } } diff --git a/UnitTests/Phantom/Workers/VariantGeneratorTests.cs b/UnitTests/Phantom/Workers/VariantGeneratorTests.cs index bcb4f8e6..5115fa20 100644 --- a/UnitTests/Phantom/Workers/VariantGeneratorTests.cs +++ b/UnitTests/Phantom/Workers/VariantGeneratorTests.cs @@ -18,10 +18,10 @@ public sealed class VariantGeneratorTests [Fact] public void GetPositionsAndRefAltAlleles_AsExpected() { - var genotypes = new[] { "1|2", "1/1", "0|1", "0/1" }; + var genotypeBlock = new GenotypeBlock(new[] { "1|2", "1/1", "0|1", "0/1" }.Select(Genotype.GetGenotype).ToArray()); var genotypeToSample = - new Dictionary<(string, int), List> {{(string.Join(";", genotypes), 0), new List {0}}}; - var indexOfUnsupportedVars = Enumerable.Repeat(new HashSet(), genotypes.Length).ToArray(); + new Dictionary> {{genotypeBlock, new List {0}}}; + var indexOfUnsupportedVars = Enumerable.Repeat(new HashSet(), genotypeBlock.Genotypes.Length).ToArray(); var starts = new[] { 356, 358, 360, 361 }; var functionBlockRanges = new List { 358, 360, 362, 364 }; var alleles = new[] { new[] { "G", "C", "T" }, new[] { "A", "T" }, new[] { "C", "G" }, new[] { "G", "T" } }; @@ -56,7 +56,7 @@ public void VariantGenerator_AsExpected() var recomposedPositions = recomposer.Recompose(new List { position1, position2, position3 }, functionBlockRanges).ToList(); Assert.Equal(2, recomposedPositions.Count); - Assert.Equal("chr1 2 . AGC AGA,GGG . PASS RECOMPOSED GT:PS . . 1|2:456", string.Join("\t", recomposedPositions[0].VcfFields)); + Assert.Equal("chr1 2 . AGC AGA,GGG,TGA . PASS RECOMPOSED GT:PS 1|3:123 . 1|2:456", string.Join("\t", recomposedPositions[0].VcfFields)); Assert.Equal("chr1 2 . AGCTG GGATC,GGGTG . PASS RECOMPOSED GT:PS . 1|2:789 .", string.Join("\t", recomposedPositions[1].VcfFields)); } @@ -140,7 +140,7 @@ public void VariantGenerator_MinQualUsed_DotIgnored() var recomposedPositions = recomposer.Recompose(new List { position1, position2, position3 }, functionBlockRanges).ToList(); Assert.Equal(2, recomposedPositions.Count); - Assert.Equal("chr1 2 . AGC AGA,GGG 45 PASS RECOMPOSED GT:PS . . 1|2:456", string.Join("\t", recomposedPositions[0].VcfFields)); + Assert.Equal("chr1 2 . AGC AGA,GGG,TGA 45 PASS RECOMPOSED GT:PS 1|3:123 . 1|2:456", string.Join("\t", recomposedPositions[0].VcfFields)); Assert.Equal("chr1 2 . AGCTG GGATC,GGGTG 30.1 PASS RECOMPOSED GT . 1|2 .", string.Join("\t", recomposedPositions[1].VcfFields)); } @@ -162,7 +162,7 @@ public void VariantGenerator_FailedFilterTagGivenCorrectly_DotTreatedAsPass() var recomposedPositions = recomposer.Recompose(new List { position1, position2, position3 }, functionBlockRanges).ToList(); Assert.Equal(2, recomposedPositions.Count); - Assert.Equal("chr1 2 . AGC AGA,GGG . PASS RECOMPOSED GT:PS . . 1|2:456", string.Join("\t", recomposedPositions[0].VcfFields)); + Assert.Equal("chr1 2 . AGC AGA,GGG,TGA . PASS RECOMPOSED GT:PS 1|3:123 . 1|2:456", string.Join("\t", recomposedPositions[0].VcfFields)); Assert.Equal("chr1 2 . AGCTG GGATC,GGGTG . FilteredVariantsRecomposed RECOMPOSED GT . 1|2 .", string.Join("\t", recomposedPositions[1].VcfFields)); } @@ -184,7 +184,7 @@ public void VariantGenerator_MinQGUsed_DotAndNullIgnored() var recomposedPositions = recomposer.Recompose(new List { position1, position2, position3 }, functionBlockRanges).ToList(); Assert.Equal(2, recomposedPositions.Count); - Assert.Equal("chr1 2 . AGC AGA,GGG . PASS RECOMPOSED GT:GQ:PS . . 1|2:15.6:456", string.Join("\t", recomposedPositions[0].VcfFields)); + Assert.Equal("chr1 2 . AGC AGA,GGG,TGA . PASS RECOMPOSED GT:GQ:PS 1|3:.:123 . 1|2:15.6:456", string.Join("\t", recomposedPositions[0].VcfFields)); Assert.Equal("chr1 2 . AGCTG GGATC,GGGTG . PASS RECOMPOSED GT:GQ . 1|2:14.2 .", string.Join("\t", recomposedPositions[1].VcfFields)); } From 2f3aecd7206c219dd3cd75d6e48fd341ad4410a2 Mon Sep 17 00:00:00 2001 From: "Roy, Rajat" Date: Thu, 19 Apr 2018 11:32:38 -0700 Subject: [PATCH 2/7] Feature/gnomad parallel 3126 (#158) Creating a parallel gnomad parser that speeds up the gnomad TSV creation. --- Nirvana/Properties/launchSettings.json | 10 + .../CreateGnomadTsv/CreateGnomadTsvMain.cs | 12 +- SAUtils/CreateGnomadTsv/GnomadTsvCreator.cs | 93 +++++++-- .../CreateIntermediateTsvsMain.cs | 2 +- SAUtils/CreateTsvIndex/CreateTsvIndexMain.cs | 45 +++++ SAUtils/DataStructures/ClinVarItem.cs | 2 +- SAUtils/DataStructures/CosmicItem.cs | 2 +- SAUtils/DataStructures/MinHeap.cs | 2 +- .../DataStructures/SupplementaryDataItem.cs | 2 +- SAUtils/DataStructures/TsvIndex.cs | 3 +- SAUtils/DegenerateBaseUtilities.cs | 11 +- SAUtils/ExtractMiniSa/ExtractMiniSaMain.cs | 5 +- SAUtils/ExtractMiniXml/ExtractMiniXmlMain.cs | 2 +- SAUtils/GeneScoresTsv/GeneScoresMain.cs | 3 +- .../Cosmic/MergedCosmicReader.cs | 11 +- .../ParallelSaTsvReader.cs | 2 +- .../MergeInterimTsvs/ConfigurationSettings.cs | 9 - SAUtils/Properties/launchSettings.json | 4 +- SAUtils/SAUtils.cs | 2 + SAUtils/SaUtilsCommon.cs | 186 ++++++++++++++++++ SAUtils/SupplementaryAnnotationUtilities.cs | 95 --------- SAUtils/TsvWriters/ClinvarTsvWriter.cs | 4 +- SAUtils/TsvWriters/LiteGnomadTsvWriter.cs | 58 ++++++ SAUtils/TsvWriters/SaMiscTsvWriter.cs | 2 +- SAUtils/TsvWriters/SaTsvWriter.cs | 19 +- VariantAnnotation/SA/SaDataBaseCommon.cs | 2 +- 26 files changed, 414 insertions(+), 174 deletions(-) create mode 100644 SAUtils/CreateTsvIndex/CreateTsvIndexMain.cs delete mode 100644 SAUtils/MergeInterimTsvs/ConfigurationSettings.cs create mode 100644 SAUtils/SaUtilsCommon.cs delete mode 100644 SAUtils/SupplementaryAnnotationUtilities.cs create mode 100644 SAUtils/TsvWriters/LiteGnomadTsvWriter.cs diff --git a/Nirvana/Properties/launchSettings.json b/Nirvana/Properties/launchSettings.json index d6317c36..174f8b70 100644 --- a/Nirvana/Properties/launchSettings.json +++ b/Nirvana/Properties/launchSettings.json @@ -59,6 +59,16 @@ "commandName": "Project", "commandLineArgs": "-i E:\\Nirvana_resources\\test_runs\\test_Phantom\\test_data\\Pedigree.vcf.gz -c E:\\Nirvana_resources\\Nirvana\\Development\\Cache\\26\\GRCh38\\Ensembl -r E:\\Nirvana_resources\\Nirvana\\Development\\References\\5\\Homo_sapiens.GRCh38.Nirvana.dat --vcf -o Pedigree_NIR_Phan_out", "workingDirectory": "E:\\Nirvana_resources\\test_runs\\test_Phantom\\" + }, + "RR_zodiacSnv": { + "commandName": "Project", + "commandLineArgs": " --cache C:\\Development\\Cache\\26\\GRCh37\\Ensembl --sd C:\\Development\\SupplementaryDatabase\\44\\GRCh37 --ref C:\\Development\\References\\5\\Homo_sapiens.GRCh37.Nirvana.dat --disable-recomposition --in debug.vcf --out debug", + "workingDirectory": "c:\\Development\\TestDatasets" + }, + "RR_momGen": { + "commandName": "Project", + "commandLineArgs": "--cache C:\\Development\\Cache\\26\\GRCh37\\Ensembl --sd C:\\Development\\SupplementaryDatabase\\45\\GRCh37 --ref C:\\Development\\References\\5\\Homo_sapiens.GRCh37.Nirvana.dat --disable-recomposition --in C:\\Development\\TestDatasets\\Mother.genome.vcf.gz --out C:\\Development\\TestDatasets\\momGen", + "workingDirectory": "C:\\Development\\TestDatasets\\" } } } \ No newline at end of file diff --git a/SAUtils/CreateGnomadTsv/CreateGnomadTsvMain.cs b/SAUtils/CreateGnomadTsv/CreateGnomadTsvMain.cs index 10cb0258..cfd008f0 100644 --- a/SAUtils/CreateGnomadTsv/CreateGnomadTsvMain.cs +++ b/SAUtils/CreateGnomadTsv/CreateGnomadTsvMain.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using CommandLine.Builders; using CommandLine.NDesk.Options; -using Compression.Utilities; using ErrorHandling; using ErrorHandling.Exceptions; using SAUtils.InputFileParsers; @@ -26,23 +24,23 @@ private ExitCodes ProgramExecution() if (!_supportedSequencingDataType.Contains(_sequencingDataType)) throw new ArgumentException($"Only the following sequencing data types are supported: {string.Join(_supportedSequencingDataType.ToString(), ", ")}"); - var inputStreamReaders = Directory.GetFiles(_inputDirectory, "*.vcf.bgz").Select(fileName => GZipUtilities.GetAppropriateStreamReader(Path.Combine(_inputDirectory, fileName))).ToArray(); + var inputFiles = Directory.GetFiles(_inputDirectory, "*.vcf.bgz"); var referenceProvider = new ReferenceSequenceProvider(FileUtilities.GetReadStream(_compressedReference)); - if (inputStreamReaders.Length == 0) + if (inputFiles.Length == 0) throw new UserErrorException("input directory does not conatin any .vcf.bgz files"); var versionFiles = Directory.GetFiles(_inputDirectory, "*.version"); if (versionFiles.Length != 1) throw new InvalidDataException("more than one .version file found in input directory"); - Console.WriteLine($"Creating gnomAD TSV file from {inputStreamReaders.Length} input files"); + Console.WriteLine($"Creating gnomAD TSV file from {inputFiles.Length} input files"); var version = DataSourceVersionReader.GetSourceVersion(versionFiles[0]); - var gnomadTsvCreator = new GnomadTsvCreator(inputStreamReaders, referenceProvider, version, _outputDirectory, _sequencingDataType); + var gnomadTsvCreator = new GnomadTsvCreator(inputFiles, referenceProvider, version, _outputDirectory, _sequencingDataType); - gnomadTsvCreator.CreateTsvs(); + gnomadTsvCreator.CreateTsvsParallel(); return ExitCodes.Success; } diff --git a/SAUtils/CreateGnomadTsv/GnomadTsvCreator.cs b/SAUtils/CreateGnomadTsv/GnomadTsvCreator.cs index 760eb03e..47b16bca 100644 --- a/SAUtils/CreateGnomadTsv/GnomadTsvCreator.cs +++ b/SAUtils/CreateGnomadTsv/GnomadTsvCreator.cs @@ -1,48 +1,115 @@ using System; +using System.Collections.Generic; using System.IO; +using System.IO.Compression; +using System.Threading.Tasks; using CommandLine.Utilities; +using Compression.FileHandling; +using Compression.Utilities; +using SAUtils.DataStructures; using SAUtils.InputFileParsers; using SAUtils.TsvWriters; using VariantAnnotation.Providers; +using VariantAnnotation.Utilities; namespace SAUtils.CreateGnomadTsv { public sealed class GnomadTsvCreator { - private readonly StreamReader[] _streamReaders; - private readonly ReferenceSequenceProvider _refProvider; + private readonly string[] _filePaths; + private readonly ReferenceSequenceProvider _refSeqProvider; private readonly DataSourceVersion _version; private readonly string _outputDirectory; private readonly string _sequencingDataType; + private const string HeaderFileName = "header.txt.gz"; - public GnomadTsvCreator(StreamReader[] streamReaders, ReferenceSequenceProvider refProvider, + private readonly Dictionary _jsonKeyDictionary = new Dictionary + { + {"genome", InterimSaCommon.GnomadTag }, + {"exome", InterimSaCommon.GnomadExomeTag } + }; + + public GnomadTsvCreator(string[] filePaths, ReferenceSequenceProvider refSeqProvider, DataSourceVersion version, string outputDirectory, string sequencingDataType) { + _filePaths = filePaths; _version = version; - _refProvider = refProvider; + _refSeqProvider = refSeqProvider; _outputDirectory = outputDirectory; - _streamReaders = streamReaders; _sequencingDataType = sequencingDataType; } - public void CreateTsvs() + public void CreateTsvsParallel() { var benchMark = new Benchmark(); - using (var writer = new GnomadTsvWriter(_version, _outputDirectory, _refProvider.GenomeAssembly, _refProvider, _sequencingDataType)) + if (_filePaths.Length > 1) { - var count = 0; - - foreach (var fileStreamReader in _streamReaders) + //create the header file + using (var headerWriter = + new StreamWriter(new BlockGZipStream(FileUtilities.GetCreateStream(Path.Combine(_outputDirectory, HeaderFileName)), + CompressionMode.Compress))) { - var reader = new GnomadReader(fileStreamReader, _refProvider.RefNameToChromosome); - TsvWriterUtilities.WriteSortedItems(reader.GetGnomadItems(), writer); - Console.WriteLine($"ingested {count++} file in " + benchMark.GetElapsedTime()); + var header = SaTsvWriter.GetHeader(_version, SaTsvCommon.SchemaVersion, _refSeqProvider.GenomeAssembly.ToString(), + "gnomad", null, true, false); + headerWriter.Write(header); } } + Parallel.ForEach(_filePaths, new ParallelOptions { MaxDegreeOfParallelism = 4 }, CreateTsvFrom); + + Console.WriteLine("Merging header and chromosome tsvs.."); + var jsonKey = _jsonKeyDictionary[_sequencingDataType]; + var fileName = jsonKey + "_" + _version.Version.Replace(" ", "_") + ".tsv.gz"; + var filePath = Path.Combine(_outputDirectory, fileName); + + SaUtilsCommon.CombineFiles(_outputDirectory, HeaderFileName, "chr*.tsv.gz", filePath, true); + + Console.WriteLine("Creating tsv index..."); + SaUtilsCommon.BuildTsvIndex(filePath); + var timeSpan = Benchmark.ToHumanReadable(benchMark.GetElapsedTime()); TsvWriterUtilities.WriteCompleteInfo("gnomAD", _version.Version, timeSpan); } + + + private void CreateTsvFrom(string filePath) + { + var benchmark = new Benchmark(); + var chrom = GetChromName(filePath); + ISaItemTsvWriter writer; + if (chrom == "all") + writer = new GnomadTsvWriter(_version, _outputDirectory, _refSeqProvider.GenomeAssembly, _refSeqProvider, + _sequencingDataType); + else + writer = new LiteGnomadTsvWriter(Path.Combine(_outputDirectory, chrom + ".tsv.gz"), _refSeqProvider); + + Console.WriteLine("Starting to parse "+chrom); + using(var tsvReader = GZipUtilities.GetAppropriateStreamReader(filePath)) + using (writer) + { + var reader = new GnomadReader(tsvReader, _refSeqProvider.RefNameToChromosome); + TsvWriterUtilities.WriteSortedItems(reader.GetGnomadItems(), writer); + } + + var timeSpan = Benchmark.ToHumanReadable(benchmark.GetElapsedTime()); + Console.WriteLine("Completed chrom " + chrom + " in:"+ timeSpan); + + } + + private string GetChromName(string filePath) + { + var pathSplits = filePath.Split(Path.PathSeparator); + var fileName = pathSplits[pathSplits.Length - 1]; + + var nameSplits = fileName.Split('.'); + + foreach (var split in nameSplits) + { + if (split.StartsWith("chr")) return split; + } + + return "all"; + } } } \ No newline at end of file diff --git a/SAUtils/CreateIntermediateTsvs/CreateIntermediateTsvsMain.cs b/SAUtils/CreateIntermediateTsvs/CreateIntermediateTsvsMain.cs index 247b36d8..ba94ac28 100644 --- a/SAUtils/CreateIntermediateTsvs/CreateIntermediateTsvsMain.cs +++ b/SAUtils/CreateIntermediateTsvs/CreateIntermediateTsvsMain.cs @@ -5,7 +5,7 @@ namespace SAUtils.CreateIntermediateTsvs { - public sealed class CreateIntermediateTsvsMain + public static class CreateIntermediateTsvsMain { private static string _compressedReference; private static string _inputDbSnpFileName; diff --git a/SAUtils/CreateTsvIndex/CreateTsvIndexMain.cs b/SAUtils/CreateTsvIndex/CreateTsvIndexMain.cs new file mode 100644 index 00000000..3f6a916b --- /dev/null +++ b/SAUtils/CreateTsvIndex/CreateTsvIndexMain.cs @@ -0,0 +1,45 @@ +using System; +using CommandLine.Builders; +using CommandLine.NDesk.Options; +using ErrorHandling; + +namespace SAUtils.CreateTsvIndex +{ + public static class CreateTsvIndexMain + { + private static string _inputTsv; + public static ExitCodes Run(string command, string[] commandArgs) + { + var ops = new OptionSet + { + { + "in|i=", + "input intermediate TSV file name", + v => _inputTsv = v + } + }; + + var commandLineExample = $"{command} [options]"; + + var exitCode = new ConsoleAppBuilder(commandArgs, ops) + .Parse() + .HasRequiredParameter(_inputTsv, "input intermediate TSV file name", "--in") + .CheckInputFilenameExists(_inputTsv, "input intermediate TSV file name", "--in") + .SkipBanner() + .ShowHelpMenu("Reads provided iTSV and generates an index file (.tvi)", commandLineExample) + .ShowErrors() + .Execute(ProgramExecution); + + return exitCode; + } + + private static ExitCodes ProgramExecution() + { + Console.WriteLine("Creating tsv index..."); + SaUtilsCommon.BuildTsvIndex(_inputTsv); + + return ExitCodes.Success; + } + + } +} \ No newline at end of file diff --git a/SAUtils/DataStructures/ClinVarItem.cs b/SAUtils/DataStructures/ClinVarItem.cs index 25a9e387..58864772 100644 --- a/SAUtils/DataStructures/ClinVarItem.cs +++ b/SAUtils/DataStructures/ClinVarItem.cs @@ -135,7 +135,7 @@ public string GetJsonString() var altAllele = string.IsNullOrEmpty(AlternateAllele) ? "-" : AlternateAllele; //the reduced alt allele should never be output - altAllele = SupplementaryAnnotationUtilities.ReverseSaReducedAllele(altAllele); + altAllele = SaUtilsCommon.ReverseSaReducedAllele(altAllele); jsonObject.AddStringValue("id", Id); jsonObject.AddStringValue("reviewStatus", ReviewStatusStrings[ReviewStatus]); diff --git a/SAUtils/DataStructures/CosmicItem.cs b/SAUtils/DataStructures/CosmicItem.cs index 9c65be72..59888668 100644 --- a/SAUtils/DataStructures/CosmicItem.cs +++ b/SAUtils/DataStructures/CosmicItem.cs @@ -121,7 +121,7 @@ public string GetJsonString() jsonObject.AddStringValue("isAlleleSpecific", IsAlleleSpecific, false); jsonObject.AddStringValue("refAllele", string.IsNullOrEmpty(ReferenceAllele) ? "-" : ReferenceAllele); jsonObject.AddStringValue("altAllele", - SupplementaryAnnotationUtilities.ReverseSaReducedAllele(AlternateAllele)); + SaUtilsCommon.ReverseSaReducedAllele(AlternateAllele)); jsonObject.AddStringValue("gene", Gene); jsonObject.AddIntValue("sampleCount", SampleCount); diff --git a/SAUtils/DataStructures/MinHeap.cs b/SAUtils/DataStructures/MinHeap.cs index 1deebe12..6ebf318b 100644 --- a/SAUtils/DataStructures/MinHeap.cs +++ b/SAUtils/DataStructures/MinHeap.cs @@ -65,7 +65,7 @@ private static void SwapItems(IList list, int i, int j) public T GetMin() { - return _itemArray.Count == 0 ? default(T) : _itemArray[0]; + return _itemArray.Count == 0 ? default : _itemArray[0]; } public int Count() diff --git a/SAUtils/DataStructures/SupplementaryDataItem.cs b/SAUtils/DataStructures/SupplementaryDataItem.cs index 1e861786..57a12f47 100644 --- a/SAUtils/DataStructures/SupplementaryDataItem.cs +++ b/SAUtils/DataStructures/SupplementaryDataItem.cs @@ -33,7 +33,7 @@ public void Trim() return; var newAlleles = BiDirectionalTrimmer.Trim(Start, ReferenceAllele, AlternateAllele); - //SupplementaryAnnotationUtilities.GetReducedAlleles(Start, ReferenceAllele, AlternateAllele); + //SaUtilsCommon.GetReducedAlleles(Start, ReferenceAllele, AlternateAllele); Start = newAlleles.Item1; ReferenceAllele = newAlleles.Item2; diff --git a/SAUtils/DataStructures/TsvIndex.cs b/SAUtils/DataStructures/TsvIndex.cs index dba69011..a527c0d6 100644 --- a/SAUtils/DataStructures/TsvIndex.cs +++ b/SAUtils/DataStructures/TsvIndex.cs @@ -73,7 +73,8 @@ public TsvIndex(BinaryReader reader) public void AddTagPosition(string tag, long position) { - TagPositions[tag] = position; + if (!TagPositions.TryAdd(tag, position)) + throw new InvalidDataException($"second block of entries for chrom:{tag}!!"); } private void WriteToFile() diff --git a/SAUtils/DegenerateBaseUtilities.cs b/SAUtils/DegenerateBaseUtilities.cs index e058562b..8a5f4d2d 100644 --- a/SAUtils/DegenerateBaseUtilities.cs +++ b/SAUtils/DegenerateBaseUtilities.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; -using System.Linq; namespace SAUtils { - public class DegenerateBaseUtilities + public static class DegenerateBaseUtilities { - public static readonly Dictionary> DegenerateBaseNotation = new Dictionary> + private static readonly Dictionary> DegenerateBaseNotation = new Dictionary> { {'B', new List{'C','G','T'}}, {'D', new List{'A','G','T'}}, @@ -19,12 +18,6 @@ public class DegenerateBaseUtilities {'Y', new List{'C','T'}} }; - public static readonly HashSet BasicBases = new HashSet {'A','C','G','T','N'}; - - public static bool HasDegenerateBase(string sequence) => - sequence.ToUpper().Any(x => DegenerateBaseNotation.ContainsKey(x)) && - sequence.ToUpper().All(x => BasicBases.Contains(x) || DegenerateBaseNotation.ContainsKey(x)); - public static List GetAllPossibleSequences(string sequenceWithDegenerateBases) { var sequences = new List(); diff --git a/SAUtils/ExtractMiniSa/ExtractMiniSaMain.cs b/SAUtils/ExtractMiniSa/ExtractMiniSaMain.cs index c216722d..ae54e9e1 100644 --- a/SAUtils/ExtractMiniSa/ExtractMiniSaMain.cs +++ b/SAUtils/ExtractMiniSa/ExtractMiniSaMain.cs @@ -35,8 +35,7 @@ private static ExitCodes ProgramExecution() public static ExitCodes Run(string command,string[] commandArgs) { - var extractor = new ExtractMiniSaMain(); - + var ops = new OptionSet { { @@ -77,7 +76,7 @@ public static ExitCodes Run(string command,string[] commandArgs) .Parse() .CheckInputFilenameExists(_inputSuppAnnotPath, "Nirvana supplementary annotations", "--in") .CheckInputFilenameExists(_compressedReference, "Compressed reference sequence file name", "--ref") - .HasRequiredParameter(_miniSaDirectory, "output directory", "--out") + .HasRequiredParameter(_miniSaDirectory, "output directory", "--out") .SkipBanner() .ShowHelpMenu("Extracts mini supplementary annotations for the given range from Nirvana Supplementary Annotations files.", commandLineExample) .ShowErrors() diff --git a/SAUtils/ExtractMiniXml/ExtractMiniXmlMain.cs b/SAUtils/ExtractMiniXml/ExtractMiniXmlMain.cs index 9002ba52..2e030974 100644 --- a/SAUtils/ExtractMiniXml/ExtractMiniXmlMain.cs +++ b/SAUtils/ExtractMiniXml/ExtractMiniXmlMain.cs @@ -43,7 +43,7 @@ public static ExitCodes Run(string command, string[] commandArgs) var exitCode = new ConsoleAppBuilder(commandArgs, ops) .Parse() .CheckInputFilenameExists(_inputXmlFile, "input XML file", "--in") - .HasRequiredParameter(_outputDir, "output directory", "--out") + .HasRequiredParameter(_outputDir, "output directory", "--out") .SkipBanner() .ShowHelpMenu("Extracts mini supplementary annotations for the given range from Nirvana Supplementary Annotations files.", commandLineExample) .ShowErrors() diff --git a/SAUtils/GeneScoresTsv/GeneScoresMain.cs b/SAUtils/GeneScoresTsv/GeneScoresMain.cs index 4e7ebf02..5cba3d75 100644 --- a/SAUtils/GeneScoresTsv/GeneScoresMain.cs +++ b/SAUtils/GeneScoresTsv/GeneScoresMain.cs @@ -23,8 +23,7 @@ private static ExitCodes ProgramExecution() public static ExitCodes Run(string command, string[] commandArgs) { - var creator = new GeneScoresMain(); - + var ops = new OptionSet { { diff --git a/SAUtils/InputFileParsers/Cosmic/MergedCosmicReader.cs b/SAUtils/InputFileParsers/Cosmic/MergedCosmicReader.cs index 6d39d46d..aaf1d5c1 100644 --- a/SAUtils/InputFileParsers/Cosmic/MergedCosmicReader.cs +++ b/SAUtils/InputFileParsers/Cosmic/MergedCosmicReader.cs @@ -225,12 +225,11 @@ internal List ExtractCosmicItems(string vcfLine) foreach (var altAllele in altAlleles) { - if (_studies.TryGetValue(cosmicId, out var studies)) - { - cosmicItems.Add(new CosmicItem(chromosome, position, cosmicId, refAllele, altAllele, _geneName, studies, _sampleCount)); - } - else cosmicItems.Add(new CosmicItem(chromosome, position, cosmicId, refAllele, altAllele, _geneName, null, _sampleCount)); - + cosmicItems.Add(_studies.TryGetValue(cosmicId, out var studies) + ? new CosmicItem(chromosome, position, cosmicId, refAllele, altAllele, _geneName, studies, + _sampleCount) + : new CosmicItem(chromosome, position, cosmicId, refAllele, altAllele, _geneName, null, + _sampleCount)); } return cosmicItems; diff --git a/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs b/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs index 63dbaede..64dc599c 100644 --- a/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs +++ b/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs @@ -132,7 +132,7 @@ private InterimSaItem ExtractItem(string line) var chromosome = columns[ChrIndex]; // position, ref and alt are in VCF style. We need to reduce them to our internal representation. - var newAlleles = SupplementaryAnnotationUtilities.GetReducedAlleles(int.Parse(columns[PositionIndex]), columns[RefAlleleIndex], columns[AltAlleleIndex]); + var newAlleles = SaUtilsCommon.GetReducedAlleles(int.Parse(columns[PositionIndex]), columns[RefAlleleIndex], columns[AltAlleleIndex]); var position = newAlleles.Item1; var refAllele = newAlleles.Item2; diff --git a/SAUtils/MergeInterimTsvs/ConfigurationSettings.cs b/SAUtils/MergeInterimTsvs/ConfigurationSettings.cs deleted file mode 100644 index 5a4678a2..00000000 --- a/SAUtils/MergeInterimTsvs/ConfigurationSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace SAUtils.MergeInterimTsvs -{ - public static class ConfigurationSettings - { - - } -} diff --git a/SAUtils/Properties/launchSettings.json b/SAUtils/Properties/launchSettings.json index 2b5ce2ce..26cce5b7 100644 --- a/SAUtils/Properties/launchSettings.json +++ b/SAUtils/Properties/launchSettings.json @@ -12,8 +12,8 @@ }, "RR_sautils": { "commandName": "Project", - "commandLineArgs": " getCosmicSvs --cnv CosmicCompleteCNA.tsv.gz --ref c:\\Development\\References\\5\\Homo_sapiens.GRCh37.Nirvana.dat -o c:\\Development\\IntermediateTsvs\\test", - "workingDirectory": "c:\\Development\\ExternalDataSources\\COSMIC\\v84\\GRCh37" + "commandLineArgs": " createSA --ref C:\\Development\\References\\5\\Homo_sapiens.GRCh37.Nirvana.dat --dir C:\\Development\\IntermediateTsvs\\test1 --out c:\\Development\\SupplementaryDatabase\\test", + "workingDirectory": "c:\\Development" }, "MITOMAP": { "commandName": "Project", diff --git a/SAUtils/SAUtils.cs b/SAUtils/SAUtils.cs index 91ee4dbe..9eb1a669 100644 --- a/SAUtils/SAUtils.cs +++ b/SAUtils/SAUtils.cs @@ -4,6 +4,7 @@ using SAUtils.CreateIntermediateTsvs; using SAUtils.CreateOmimTsv; using SAUtils.CreateTopMedTsv; +using SAUtils.CreateTsvIndex; using SAUtils.DbSnpRemapper; using SAUtils.ExtractCosmicSvs; using SAUtils.ExtractMiniSa; @@ -29,6 +30,7 @@ public static int Main(string[] args) ["createGnomadTsv"] = new TopLevelOption("create gnomAD tsv file", CreateGnomadTsvMain.Run), ["createTopMedTsv"] = new TopLevelOption("create TOPMed tsv file", CreateTopMedTsvMain.Run), ["createCosmicSvs"] = new TopLevelOption("create COSMIC SV tsv files", ExtractCosmicSvsMain.Run), + ["createTsvIndex"] = new TopLevelOption("create index (tvi) from a tsv file", CreateTsvIndexMain.Run), ["remapWithDbsnp"] = new TopLevelOption("remap a VCF file given source and destination rsID mappings", DbSnpRemapperMain.Run), }; diff --git a/SAUtils/SaUtilsCommon.cs b/SAUtils/SaUtilsCommon.cs new file mode 100644 index 00000000..751e4c55 --- /dev/null +++ b/SAUtils/SaUtilsCommon.cs @@ -0,0 +1,186 @@ +using System; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using CommandLine.Utilities; +using CommonUtilities; +using Compression.FileHandling; +using SAUtils.DataStructures; +using VariantAnnotation.Interface.Providers; +using VariantAnnotation.Utilities; + +namespace SAUtils +{ + public static class SaUtilsCommon + { + /// + /// Returns a regular alternate allele when a provided with one have SA format. + /// In case of long insertions or InsDel, where the saAltAllele contains an MD5 hash, the hash is returned. + /// + /// supplementary annotation alternate allele + /// The way the calling function wants to represent an empty allele + /// regular alternate allele + public static string ReverseSaReducedAllele(string saAltAllele, string emptyAllele = "-") + { + if (saAltAllele == null) return null; + if (saAltAllele.All(Char.IsDigit)) return emptyAllele; // this was a deletion + + int firstBaseIndex; + for (firstBaseIndex = 0; firstBaseIndex < saAltAllele.Length; firstBaseIndex++) + { + if (saAltAllele[firstBaseIndex] != 'i' && saAltAllele[firstBaseIndex] != '<' && + !Char.IsDigit(saAltAllele[firstBaseIndex])) + break; + } + + if (saAltAllele.Substring(firstBaseIndex) == "") return emptyAllele; + + return firstBaseIndex > 0 && firstBaseIndex < saAltAllele.Length + ? saAltAllele.Substring(firstBaseIndex) + : saAltAllele; + } + + private const int ReferenceWindow = 10; + + public static bool ValidateReference(string chromosome, int position, string refAllele, ISequenceProvider sequenceProvider) + { + if (sequenceProvider == null) return true; + + var refDictionary = sequenceProvider.RefNameToChromosome; + if (!refDictionary.ContainsKey(chromosome)) return false; + + var chrom = refDictionary[chromosome]; + + sequenceProvider.LoadChromosome(chrom); + var refSequence = sequenceProvider.Sequence.Substring(position - 1, ReferenceWindow); + return ValidateRefAllele(refAllele, refSequence); + } + public static bool ValidateRefAllele(string refAllele, string refBases) + { + if (refBases == null) return true; + if (refAllele == ".") return true; //ref base is unknown + if (refBases.All(x => x == 'N')) return true; + + return refAllele.Length < refBases.Length ? refBases.StartsWith(refAllele) : refAllele.StartsWith(refBases); + + // in rare cases the refAllele will be too large for our refBases string that is limited in length + } + + public static void BuildTsvIndex(string filePath) + { + var benchMark = new Benchmark(); + using (var newIndex = new TsvIndex(filePath + ".tvi")) + using (var reader = new BgzipTextReader(new BlockGZipStream(FileUtilities.GetReadStream(filePath), CompressionMode.Decompress))) + { + string line; + var chrom = ""; + while ((line = reader.ReadLine()) != null) + { + if (line.StartsWith("#")) continue; + var currentChrom = GetChrom(line); + + if (chrom == currentChrom) continue; + + chrom = currentChrom; + Console.WriteLine($"Chrom:{chrom}, position:{reader.Position}"); + newIndex.AddTagPosition(chrom, reader.Position); + + } + + } + var timeSpan = Benchmark.ToHumanReadable(benchMark.GetElapsedTime()); + Console.WriteLine($"Indexing time:{timeSpan}"); + + } + + private static string GetChrom(string line) + { + var firstTabIndex = line.IndexOf('\t'); + + return line.Substring(0, firstTabIndex); + } + + public static void CombineFiles(string inputDirectoryPath, string headerFileName, string inputFileNamePattern, string outputFilePath, bool deleteInputFiles = false) + { + var benchMark = new Benchmark(); + string[] inputFilePaths = Directory.GetFiles(inputDirectoryPath, inputFileNamePattern); + Console.WriteLine($"Number of files: {inputFilePaths.Length}."); + using (var outputStream = File.Create(outputFilePath)) + { + using (var headerStream = File.OpenRead(Path.Combine(inputDirectoryPath, headerFileName))) + { + headerStream.CopyTo(outputStream); + } + + foreach (var inputFilePath in inputFilePaths) + { + using (var inputStream = File.OpenRead(inputFilePath)) + { + // Buffer size can be passed as the second argument. + inputStream.CopyTo(outputStream); + } + Console.WriteLine($"The file {inputFilePath} has been processed."); + } + } + var timeSpan = Benchmark.ToHumanReadable(benchMark.GetElapsedTime()); + Console.WriteLine($"File concatenation time:{timeSpan}"); + + if (!deleteInputFiles) return; + + Console.WriteLine("Deleting input files"); + foreach (var inputFilePath in inputFilePaths) + { + File.Delete(inputFilePath); + } + + } + + public static (int Start, string RefAllele, string AltAllele) GetReducedAlleles(int start, string refAllele, string altAllele) + { + // we have a deletion + if (refAllele == "-") refAllele = ""; + if (altAllele == "-") altAllele = ""; + if (!NeedsReduction(refAllele, altAllele)) + return (start, refAllele, altAllele); + + var trimmedTuple = BiDirectionalTrimmer.Trim(start, refAllele, altAllele); + + start = trimmedTuple.Start; + refAllele = trimmedTuple.RefAllele; + altAllele = trimmedTuple.AltAllele; + + // we have detected a deletion after trimming + if (String.IsNullOrEmpty(altAllele)) + return (start, refAllele, refAllele.Length.ToString(CultureInfo.InvariantCulture)); + + // we have an insertion and we indicate that with an i at the beginning + if (String.IsNullOrEmpty(refAllele)) + return (start, refAllele, 'i' + altAllele); + + if (refAllele.Length == altAllele.Length) //SNV or CNV + return (start, refAllele, altAllele); + + // its a delins + altAllele = refAllele.Length.ToString(CultureInfo.InvariantCulture) + altAllele; + + return (start, refAllele, altAllele); + } + + private static bool NeedsReduction(string refAllele, string altAllele) + { + if (String.IsNullOrEmpty(altAllele)) return true; + + if (!String.IsNullOrEmpty(refAllele) && altAllele.All(x => x == 'N')) return false; + + return !(altAllele[0] == 'i' || altAllele[0] == '<' || Char.IsDigit(altAllele[0])); + } + + public static string ConvertToVcfInfoString(string s) + { + //characters such as comma, space, etc. are not allowed in vcfinfo strings. + s = s.Replace(" ", "_"); + return s.Replace(",", "\\x2c"); + } + } +} diff --git a/SAUtils/SupplementaryAnnotationUtilities.cs b/SAUtils/SupplementaryAnnotationUtilities.cs deleted file mode 100644 index 629c55e2..00000000 --- a/SAUtils/SupplementaryAnnotationUtilities.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Globalization; -using System.Linq; -using CommonUtilities; - -namespace SAUtils -{ - public static class SupplementaryAnnotationUtilities - { - /// - /// Returns a regular alternate allele when a provided with one have SA format. - /// In case of long insertions or InsDel, where the saAltAllele contains an MD5 hash, the hash is returned. - /// - /// supplementary annotation alternate allele - /// The way the calling function wants to represent an empty allele - /// regular alternate allele - public static string ReverseSaReducedAllele(string saAltAllele, string emptyAllele = "-") - { - if (saAltAllele == null) return null; - if (saAltAllele.All(char.IsDigit)) return emptyAllele; // this was a deletion - - int firstBaseIndex; - for (firstBaseIndex = 0; firstBaseIndex < saAltAllele.Length; firstBaseIndex++) - { - if (saAltAllele[firstBaseIndex] != 'i' && saAltAllele[firstBaseIndex] != '<' && - !char.IsDigit(saAltAllele[firstBaseIndex])) - break; - } - - if (saAltAllele.Substring(firstBaseIndex) == "") return emptyAllele; - - return firstBaseIndex > 0 && firstBaseIndex < saAltAllele.Length - ? saAltAllele.Substring(firstBaseIndex) - : saAltAllele; - } - - public static bool ValidateRefAllele(string refAllele, string refBases) - { - if (refBases == null) return true; - if (refAllele == ".") return true; //ref base is unknown - if (refBases.All(x => x == 'N')) return true; - - return refAllele.Length < refBases.Length ? refBases.StartsWith(refAllele) : refAllele.StartsWith(refBases); - - // in rare cases the refAllele will be too large for our refBases string that is limited in length - } - - public static (int Start, string RefAllele, string AltAllele) GetReducedAlleles(int start, string refAllele, string altAllele) - { - // we have a deletion - if (refAllele == "-") refAllele = ""; - if (altAllele == "-") altAllele = ""; - if (!NeedsReduction(refAllele, altAllele)) - return (start, refAllele, altAllele); - - var trimmedTuple = BiDirectionalTrimmer.Trim(start, refAllele, altAllele); - - start = trimmedTuple.Start; - refAllele = trimmedTuple.RefAllele; - altAllele = trimmedTuple.AltAllele; - - // we have detected a deletion after trimming - if (string.IsNullOrEmpty(altAllele)) - return (start, refAllele, refAllele.Length.ToString(CultureInfo.InvariantCulture)); - - // we have an insertion and we indicate that with an i at the beginning - if (string.IsNullOrEmpty(refAllele)) - return (start, refAllele, 'i' + altAllele); - - if (refAllele.Length == altAllele.Length) //SNV or CNV - return (start, refAllele, altAllele); - - // its a delins - altAllele = refAllele.Length.ToString(CultureInfo.InvariantCulture) + altAllele; - - return (start, refAllele, altAllele); - } - - private static bool NeedsReduction(string refAllele, string altAllele) - { - if (string.IsNullOrEmpty(altAllele)) return true; - - if (!string.IsNullOrEmpty(refAllele) && altAllele.All(x => x == 'N')) return false; - - return !(altAllele[0] == 'i' || altAllele[0] == '<' || char.IsDigit(altAllele[0])); - } - - public static string ConvertToVcfInfoString(string s) - { - //characters such as comma, space, etc. are not allowed in vcfinfo strings. - s = s.Replace(" ", "_"); - return s.Replace(",", "\\x2c"); - } - - } -} diff --git a/SAUtils/TsvWriters/ClinvarTsvWriter.cs b/SAUtils/TsvWriters/ClinvarTsvWriter.cs index 4cb517c9..a07203f1 100644 --- a/SAUtils/TsvWriters/ClinvarTsvWriter.cs +++ b/SAUtils/TsvWriters/ClinvarTsvWriter.cs @@ -82,7 +82,7 @@ public void WritePosition(IEnumerable saItems) var altAllele = kvp.Key.AlternateAllele; var groupedItems = kvp.Value; - var vcfString = string.Join(",", groupedItems.OrderBy(x => x.Id).Select(x => SupplementaryAnnotationUtilities.ConvertToVcfInfoString(x.Significance))); + var vcfString = string.Join(",", groupedItems.OrderBy(x => x.Id).Select(x => SaUtilsCommon.ConvertToVcfInfoString(x.Significance))); var jsonStrings = groupedItems.OrderBy(x => x.Id).Select(x => x.GetJsonString()).ToList(); var firstItem = groupedItems[0]; @@ -96,7 +96,7 @@ public void WritePosition(IEnumerable saItems) //foreach (var groupedItem in alleleGroupedItems) //{ // var uniqueItems = groupedItem.GroupBy(p => p.ID).Select(x => x.First()).ToList(); - // var vcfString = string.Join(",", uniqueItems.Select(x => SupplementaryAnnotationUtilities.ConvertToVcfInfoString(x.Significance))); + // var vcfString = string.Join(",", uniqueItems.Select(x => SaUtilsCommon.ConvertToVcfInfoString(x.Significance))); // var jsonStrings = uniqueItems.Select(x => x.GetVariantJsonString()).ToList(); // // since the reference allele for different items in the group may be different, we only use the first base as it is supposed to be the common padding base. diff --git a/SAUtils/TsvWriters/LiteGnomadTsvWriter.cs b/SAUtils/TsvWriters/LiteGnomadTsvWriter.cs new file mode 100644 index 00000000..893cb7f1 --- /dev/null +++ b/SAUtils/TsvWriters/LiteGnomadTsvWriter.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using Compression.FileHandling; +using SAUtils.DataStructures; +using VariantAnnotation.Interface.Providers; +using VariantAnnotation.Utilities; + +namespace SAUtils.TsvWriters +{ + public sealed class LiteGnomadTsvWriter : ISaItemTsvWriter + { + private readonly StreamWriter _writer; + private readonly ISequenceProvider _sequenceProvider; + public void Dispose() + { + _writer?.Dispose(); + } + + public LiteGnomadTsvWriter(string fileName, ISequenceProvider sequenceProvider) + { + _sequenceProvider = sequenceProvider; + _writer = new StreamWriter(new BlockGZipStream(FileUtilities.GetCreateStream(fileName), CompressionMode.Compress)); + } + + public void WritePosition(IEnumerable saItems) + { + if (saItems == null) return; + + var gnomadItems = new List(); + foreach (var item in saItems) + { + if (!(item is GnomadItem gnomadItem)) + throw new InvalidDataException("Expected GnomadItems list!!"); + gnomadItems.Add(gnomadItem); + } + + SupplementaryDataItem.RemoveConflictingAlleles(gnomadItems); + + + foreach (var gnomadItem in gnomadItems) + { + AddEntry(gnomadItem.Chromosome.EnsemblName, gnomadItem.Start, gnomadItem.ReferenceAllele, gnomadItem.AlternateAllele, gnomadItem.GetJsonString()); + } + } + + private void AddEntry(string chromosome, int position, string refAllele, string altAllele, string jsonString) + { + if (string.IsNullOrEmpty(jsonString)) return; + if (!SaUtilsCommon.ValidateReference(chromosome, position, refAllele, _sequenceProvider)) return; + + refAllele = string.IsNullOrEmpty(refAllele) ? "-" : refAllele; + altAllele = string.IsNullOrEmpty(altAllele) ? "-" : altAllele; + + _writer.WriteLine($"{chromosome}\t{position}\t{refAllele}\t{altAllele}\t\t{jsonString}"); + } + } +} \ No newline at end of file diff --git a/SAUtils/TsvWriters/SaMiscTsvWriter.cs b/SAUtils/TsvWriters/SaMiscTsvWriter.cs index db900d3f..230d4296 100644 --- a/SAUtils/TsvWriters/SaMiscTsvWriter.cs +++ b/SAUtils/TsvWriters/SaMiscTsvWriter.cs @@ -111,7 +111,7 @@ private bool ValidateReference(string chromosome, int position, string refAllele _sequenceProvider.LoadChromosome(chrom); var refSequence = _sequenceProvider.Sequence.Substring(position - 1, ReferenceWindow); - return SupplementaryAnnotationUtilities.ValidateRefAllele(refAllele, refSequence); + return SaUtilsCommon.ValidateRefAllele(refAllele, refSequence); } } diff --git a/SAUtils/TsvWriters/SaTsvWriter.cs b/SAUtils/TsvWriters/SaTsvWriter.cs index cfc8efbe..d336d43a 100644 --- a/SAUtils/TsvWriters/SaTsvWriter.cs +++ b/SAUtils/TsvWriters/SaTsvWriter.cs @@ -13,7 +13,6 @@ public sealed class SaTsvWriter : IDisposable { #region members - private const int ReferenceWindow = 10; private readonly BgzipTextWriter _bgzipTextWriter; private readonly TsvIndex _tsvIndex; private string _currentChromosome; @@ -76,7 +75,7 @@ private SaTsvWriter(string outputDir, DataSourceVersion dataSourceVersion, strin } - private static string GetHeader(DataSourceVersion dataSourceVersion, int schemaVersion, string assembly, string jsonKey, string vcfKeys, bool matchByAllele, bool isArray) + public static string GetHeader(DataSourceVersion dataSourceVersion, int schemaVersion, string assembly, string jsonKey, string vcfKeys, bool matchByAllele, bool isArray) { var sb = StringBuilderCache.Acquire(); @@ -101,7 +100,7 @@ public void AddEntry(string chromosome, int position, string refAllele, string a if ((jsonStrings == null || jsonStrings.Count == 0) && string.IsNullOrEmpty(vcfString)) return; //validate the vcf reference - if (!ValidateReference(chromosome, position, refAllele)) return; + if (!SaUtilsCommon.ValidateReference(chromosome, position, refAllele, _sequenceProvider)) return; if (!chromosome.Equals(_currentChromosome)) { @@ -126,18 +125,6 @@ public void AddEntry(string chromosome, int position, string refAllele, string a _bgzipTextWriter.Write("\n"); } - private bool ValidateReference(string chromosome, int position, string refAllele) - { - if (_sequenceProvider == null) return true; - - var refDictionary = _sequenceProvider.RefNameToChromosome; - if (!refDictionary.ContainsKey(chromosome)) return false; - - var chrom = refDictionary[chromosome]; - - _sequenceProvider.LoadChromosome(chrom); - var refSequence = _sequenceProvider.Sequence.Substring(position - 1, ReferenceWindow); - return SupplementaryAnnotationUtilities.ValidateRefAllele(refAllele, refSequence); - } + } } diff --git a/VariantAnnotation/SA/SaDataBaseCommon.cs b/VariantAnnotation/SA/SaDataBaseCommon.cs index 1c87690e..29f046f4 100644 --- a/VariantAnnotation/SA/SaDataBaseCommon.cs +++ b/VariantAnnotation/SA/SaDataBaseCommon.cs @@ -5,7 +5,7 @@ public static class SaDataBaseCommon public const uint GuardInt = 4041327495; public const string DataHeader = "NirvanaData"; - public const ushort DataVersion = 44; + public const ushort DataVersion = 45; public const ushort SchemaVersion = 21; public const double RefMinorThreshold = 0.95; From 373d023a97cf218554a68803cc4ff969d692e97f Mon Sep 17 00:00:00 2001 From: "Roy, Rajat" Date: Thu, 19 Apr 2018 13:38:10 -0700 Subject: [PATCH 3/7] making phyloP a position object in vcf output (#171) Phylop is not positional in VCF output --- .../IO/VcfWriter/LiteVcfWriterTests.cs | 2 +- .../IO/VcfWriter/VcfConversionTests.cs | 20 ++++++++++ .../IO/VcfWriter/LiteVcfWriter.cs | 2 +- .../IO/VcfWriter/VcfConversion.cs | 38 +++++++++---------- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/UnitTests/VariantAnnotation/IO/VcfWriter/LiteVcfWriterTests.cs b/UnitTests/VariantAnnotation/IO/VcfWriter/LiteVcfWriterTests.cs index 81b29081..d1d58733 100644 --- a/UnitTests/VariantAnnotation/IO/VcfWriter/LiteVcfWriterTests.cs +++ b/UnitTests/VariantAnnotation/IO/VcfWriter/LiteVcfWriterTests.cs @@ -28,7 +28,7 @@ public sealed class LiteVcfWriterTests "##INFO=", "##INFO=", "##INFO=", - "##INFO=" + "##INFO=" }; [Fact] diff --git a/UnitTests/VariantAnnotation/IO/VcfWriter/VcfConversionTests.cs b/UnitTests/VariantAnnotation/IO/VcfWriter/VcfConversionTests.cs index 46f63834..50b75c54 100644 --- a/UnitTests/VariantAnnotation/IO/VcfWriter/VcfConversionTests.cs +++ b/UnitTests/VariantAnnotation/IO/VcfWriter/VcfConversionTests.cs @@ -116,6 +116,26 @@ public void Gmaf_outputed() Assert.Equal("GMAF=G|0.002", observedVcf); } + [Fact] + public void Phylop_positional() + { + var vcfFields = "chr1 101 sa123 A T . . .".Split("\t"); + var chrom = new Chromosome("chr1", "1", 0); + var inforData = new InfoData(null, null, VariantType.SNV, null, null, null, null, null, false, null, null, + false, false, "", null, null); + var position = new Position(chrom, 101, 101, "A", new[] { "T" }, 100, null, null, null, inforData, vcfFields, new[] { false }, false); + var variant = new Variant(chrom, 101, 101, "A", "T", VariantType.SNV, null, false, false, false, null, null, new AnnotationBehavior(true, false, false, true, false, false)); + var annotatedVariant = new AnnotatedVariant(variant); + + annotatedVariant.PhylopScore = -0.567; + IAnnotatedVariant[] annotatedVariants = { annotatedVariant }; + var annotatedPosition = new AnnotatedPosition(position, annotatedVariants); + + var converter = new VcfConversion(); + var observedVcf = converter.Convert(annotatedPosition).Split("\t")[VcfCommon.InfoIndex]; + + Assert.Equal("phyloP=-0.567", observedVcf); + } [Fact] diff --git a/VariantAnnotation/IO/VcfWriter/LiteVcfWriter.cs b/VariantAnnotation/IO/VcfWriter/LiteVcfWriter.cs index 553620c7..c73d1643 100644 --- a/VariantAnnotation/IO/VcfWriter/LiteVcfWriter.cs +++ b/VariantAnnotation/IO/VcfWriter/LiteVcfWriter.cs @@ -31,7 +31,7 @@ public sealed class LiteVcfWriter : IDisposable "##INFO=\n" + "##INFO=\n" + "##INFO=\n" + - "##INFO="; + "##INFO="; #region IDisposable diff --git a/VariantAnnotation/IO/VcfWriter/VcfConversion.cs b/VariantAnnotation/IO/VcfWriter/VcfConversion.cs index 58c1feff..4ff12de6 100644 --- a/VariantAnnotation/IO/VcfWriter/VcfConversion.cs +++ b/VariantAnnotation/IO/VcfWriter/VcfConversion.cs @@ -34,11 +34,11 @@ public string Convert(IAnnotatedPosition annotatedPosition) } // add dbSNP id - var dbSnpId = ExtractDbId(annotatedPosition); + string dbSnpId = ExtractDbId(annotatedPosition); _sb.Append(dbSnpId); _sb.Append('\t'); - for (var vcfIndex = VcfCommon.IdIndex + 1; vcfIndex < VcfCommon.InfoIndex; vcfIndex++) + for (int vcfIndex = VcfCommon.IdIndex + 1; vcfIndex < VcfCommon.InfoIndex; vcfIndex++) { _sb.Append(fields[vcfIndex]); _sb.Append('\t'); @@ -47,8 +47,8 @@ public string Convert(IAnnotatedPosition annotatedPosition) AddInfoField(annotatedPosition, _sb); // add all of the fields after the info field - var numColumns = fields.Length; - for (var vcfIndex = VcfCommon.InfoIndex + 1; vcfIndex < numColumns; vcfIndex++) + int numColumns = fields.Length; + for (int vcfIndex = VcfCommon.InfoIndex + 1; vcfIndex < numColumns; vcfIndex++) { _sb.Append('\t'); _sb.Append(fields[vcfIndex]); @@ -63,14 +63,14 @@ private static string ExtractDbId(IAnnotatedPosition annotatedPosition) var nonDbsnpIds = GetNonDbsnpIds(annotatedPosition.Position.VcfFields[VcfCommon.IdIndex]); - if (nonDbsnpIds != null) foreach (var nonDbsnpId in nonDbsnpIds) dbSnp.Add(nonDbsnpId); + if (nonDbsnpIds != null) foreach (string nonDbsnpId in nonDbsnpIds) dbSnp.Add(nonDbsnpId); foreach (var annotatedVariant in annotatedPosition.AnnotatedVariants) { foreach (var suppAnnotation in annotatedVariant.SupplementaryAnnotations) { if (suppAnnotation.SaDataSource.KeyName != DbSnpKeyName) continue; - foreach (var s in suppAnnotation.GetVcfStrings()) + foreach (string s in suppAnnotation.GetVcfStrings()) { dbSnp.Add(s); } @@ -92,7 +92,7 @@ private static IEnumerable GetNonDbsnpIds(string idField) private void AddInfoField(IAnnotatedPosition annotatedPosition, StringBuilder sb) { var infoEntries = new VcfField(); - var infoField = annotatedPosition.Position.InfoData.UpdatedInfoField; + string infoField = annotatedPosition.Position.InfoData.UpdatedInfoField; if (!string.IsNullOrEmpty(infoField)) { @@ -128,11 +128,11 @@ private static void ExtractInfo(IAnnotatedPosition annotatedPosition, VcfField i { var alleleFreq1000G = new VcfInfoKeyValue("AF1000G"); var ancestralAllele = new VcfPositionalInfo("AA"); - var phyloP = new VcfInfoKeyValue("phyloP"); + var phyloP = new VcfPositionalInfo("phyloP"); var suppAnnotationSources = new Dictionary(); var isSaArrayInfo = new Dictionary(); - var numInputAltAlleles = annotatedPosition.Position.AltAlleles.Length; + int numInputAltAlleles = annotatedPosition.Position.AltAlleles.Length; foreach (var alternateAllele in annotatedPosition.AnnotatedVariants) { @@ -160,13 +160,13 @@ private static void ExtractInfo(IAnnotatedPosition annotatedPosition, VcfField i var inputGenotypeIndex = GetInputGenotypeIndex(annotatedPosition.Position.AltAlleles, annotatedPosition.AnnotatedVariants); // understand the number of annotation contains in the whole vcf line - for (int i = 0; i < annotatedPosition.AnnotatedVariants.Length; i++) + for (var i = 0; i < annotatedPosition.AnnotatedVariants.Length; i++) { var annotatedVariant = annotatedPosition.AnnotatedVariants[i]; - var genotypeIndex = inputGenotypeIndex[i] + 1; + int genotypeIndex = inputGenotypeIndex[i] + 1; if (annotatedVariant.Variant.IsRefMinor) infoEntries.Add("RefMinor"); - phyloP.Add(annotatedVariant.PhylopScore?.ToString(CultureInfo.InvariantCulture)); + phyloP.AddValue(annotatedVariant.PhylopScore?.ToString(CultureInfo.InvariantCulture)); foreach (var sa in annotatedVariant.SupplementaryAnnotations) { @@ -174,15 +174,15 @@ private static void ExtractInfo(IAnnotatedPosition annotatedPosition, VcfField i if (sa.SaDataSource.KeyName == DbSnpKeyName) continue; if (sa.SaDataSource.KeyName == RefMinorKeyName) continue; - foreach (var vcfAnnotation in sa.GetVcfStrings()) + foreach (string vcfAnnotation in sa.GetVcfStrings()) { if (string.IsNullOrEmpty(vcfAnnotation)) continue; if (sa.SaDataSource.KeyName == OneKgKeyName) { var contents = vcfAnnotation.Split(';'); - var freq = contents[0]; - var ancestryAllele = string.IsNullOrEmpty(contents[1]) ? null : contents[1]; + string freq = contents[0]; + string ancestryAllele = string.IsNullOrEmpty(contents[1]) ? null : contents[1]; alleleFreq1000G.Add(freq, genotypeIndex); ancestralAllele.AddValue(ancestryAllele); @@ -259,7 +259,7 @@ private static string GetCsqrString(CsqEntry csq) private string GetCsqtAndCsqrVcfInfo(List csqList) { // make sure we have some tags - var numCsqTags = csqList.Count; + int numCsqTags = csqList.Count; if (numCsqTags == 0) return null; // build our vcf INFO fields @@ -287,8 +287,8 @@ private string GetCsqtAndCsqrVcfInfo(List csqList) } } - var hasCsqT = _csqtStrings.Count > 0; - var hasCsqR = _csqrStrings.Count > 0; + bool hasCsqT = _csqtStrings.Count > 0; + bool hasCsqR = _csqrStrings.Count > 0; if (hasCsqT) _csqInfoBuilder.Append("CSQT=" + string.Join(",", _csqtStrings)); if (hasCsqT && hasCsqR) _csqInfoBuilder.Append(';'); @@ -301,7 +301,7 @@ private static void ExtractCsqs(IAnnotatedPosition unifiedJson, List c { for (int i = 0; i < unifiedJson.AnnotatedVariants.Length; i++) { - var genotypeIndex = i + 1; + int genotypeIndex = i + 1; var jsonVariant = unifiedJson.AnnotatedVariants[i]; csqs.AddRange( From 71bcd9ebe893443f9028caeafa03611b90ce001c Mon Sep 17 00:00:00 2001 From: "Roy, Rajat" Date: Mon, 23 Apr 2018 14:34:19 -0700 Subject: [PATCH 4/7] Bugfix/otf jasix 3199 (#172) * using ucsc naming styles since that is the style the json output follows * adding a map of chrom names in index * regenerating jasix index for unit tests * added unit test for query in both enesmbl and ucsc style for on the fly index generation * using tuple names * using TryGetValue * reorganizing the jasix main file --- Jasix/ConfigurationSettings.cs | 15 -- Jasix/DataStructures/JasixIndex.cs | 68 +++++++-- Jasix/Jasix.cs | 137 ++++++++++-------- Jasix/OnTheFlyIndexCreator.cs | 4 +- Jasix/Properties/launchSettings.json | 2 +- Jasix/QueryProcessor.cs | 14 +- Nirvana/Properties/launchSettings.json | 2 +- UnitTests/Jasix/JasixQueryProcessingTests.cs | 20 +++ UnitTests/Resources/Clinvar20150901.json.gz | Bin 0 -> 274829 bytes .../Resources/Clinvar20150901.json.gz.jsi | Bin 0 -> 2936 bytes UnitTests/Resources/JasixTest.json.gz.jsi | Bin 1690 -> 1691 bytes .../Resources/cosmicv72.indels.json.gz.jsi | Bin 3002 -> 3003 bytes 12 files changed, 161 insertions(+), 101 deletions(-) delete mode 100644 Jasix/ConfigurationSettings.cs create mode 100644 UnitTests/Resources/Clinvar20150901.json.gz create mode 100644 UnitTests/Resources/Clinvar20150901.json.gz.jsi diff --git a/Jasix/ConfigurationSettings.cs b/Jasix/ConfigurationSettings.cs deleted file mode 100644 index e505534b..00000000 --- a/Jasix/ConfigurationSettings.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace Jasix -{ - public static class ConfigurationSettings - { - public static string InputJson; - public static string OutputFile; - public static readonly List Queries=new List(); - public static bool PrintHeader; - public static bool PrintHeaderOnly; - public static bool ListChromosomeName; - public static bool CreateIndex; - } -} \ No newline at end of file diff --git a/Jasix/DataStructures/JasixIndex.cs b/Jasix/DataStructures/JasixIndex.cs index c6575413..98ba11ad 100644 --- a/Jasix/DataStructures/JasixIndex.cs +++ b/Jasix/DataStructures/JasixIndex.cs @@ -9,6 +9,7 @@ namespace Jasix.DataStructures public sealed class JasixIndex { private readonly Dictionary _chrIndices; + private readonly Dictionary _synonymToChrName; public string HeaderLine; // the json file might contain sections. We want to be able to index these sections too @@ -16,6 +17,7 @@ public sealed class JasixIndex public JasixIndex() { _chrIndices = new Dictionary(); + _synonymToChrName = new Dictionary(); } private JasixIndex(IExtendedBinaryReader reader):this() @@ -32,6 +34,15 @@ private JasixIndex(IExtendedBinaryReader reader):this() var chrIndex = new JasixChrIndex(reader); _chrIndices[chrIndex.ReferenceSequence]= chrIndex; } + + int synonymCount = reader.ReadOptInt32(); + if (synonymCount == 0) return; + for (var i = 0; i < synonymCount; i++) + { + string synonym = reader.ReadAsciiString(); + string indexName = reader.ReadAsciiString(); + _synonymToChrName[synonym] = indexName; + } } public JasixIndex(Stream stream) : this(new ExtendedBinaryReader(stream)) @@ -52,6 +63,14 @@ public void Write(Stream writeStream) { chrIndex.Write(writer); } + + writer.WriteOpt(_synonymToChrName.Count); + if (_synonymToChrName.Count == 0) return; + foreach (var pair in _synonymToChrName) + { + writer.Write(pair.Key); + writer.Write(pair.Value); + } } public void Flush() @@ -62,15 +81,24 @@ public void Flush() } } - public void Add(string chr, int start, int end, long fileLoc) + public void Add(string chr, int start, int end, long fileLoc, string chrSynonym=null) { + if (!string.IsNullOrEmpty(chrSynonym)) + { + _synonymToChrName[chrSynonym] = chr; + } + + if (_chrIndices.TryGetValue(chr, out var chrIndex)) + { + chrIndex.Add(start, end, fileLoc); + } + else + { + _chrIndices[chr] = new JasixChrIndex(chr); + _chrIndices[chr].Add(start, end, fileLoc); + + } - if (!_chrIndices.ContainsKey(chr)) - { - _chrIndices[chr] = new JasixChrIndex(chr); - } - - _chrIndices[chr].Add(start, end, fileLoc); } @@ -79,9 +107,14 @@ public long GetFirstVariantPosition(string chr, int start, int end) { if (_chrIndices == null || _chrIndices.Count == 0) return -1; - if (!_chrIndices.ContainsKey(chr)) return -1; - - return _chrIndices[chr].FindFirstSmallVariant(start, end); + if (_synonymToChrName.TryGetValue(chr, out string indexName)) + chr = indexName; + + if (_chrIndices.TryGetValue(chr, out var chrIndex)) + { + return chrIndex.FindFirstSmallVariant(start, end); + } + return -1; } @@ -90,7 +123,10 @@ public long[] LargeVariantPositions(string chr, int begin, int end) { if (_chrIndices == null || _chrIndices.Count == 0) return null; - return !_chrIndices.ContainsKey(chr) ? null : _chrIndices[chr].FindLargeVariants(begin, end); + if (_synonymToChrName.TryGetValue(chr, out string indexName)) + chr = indexName; + + return _chrIndices.TryGetValue(chr, out var chrIndex) ? chrIndex.FindLargeVariants(begin, end) : null; } public IEnumerable GetChromosomeList() @@ -100,7 +136,15 @@ public IEnumerable GetChromosomeList() public bool ContainsChr(string chr) { - return _chrIndices.Keys.Contains(chr); + if (_synonymToChrName.TryGetValue(chr, out string indexName)) + return _chrIndices.Keys.Contains(indexName); + return _chrIndices.Keys.Contains(chr); + } + + public string GetIndexChromName(string chromName) + { + if (_chrIndices.ContainsKey(chromName)) return chromName; + return _synonymToChrName.TryGetValue(chromName, out string indexName) ? indexName : null; } } } diff --git a/Jasix/Jasix.cs b/Jasix/Jasix.cs index 9e61fd57..01ef295e 100644 --- a/Jasix/Jasix.cs +++ b/Jasix/Jasix.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using CommandLine.Builders; using CommandLine.NDesk.Options; @@ -11,62 +12,15 @@ namespace Jasix { - public sealed class Jasix + public static class Jasix { - private static ExitCodes ProgramExecution() - { - if (ConfigurationSettings.CreateIndex) - { - using (var indexCreator = new IndexCreator(ConfigurationSettings.InputJson)) - { - indexCreator.CreateIndex(); - } - - return ExitCodes.Success; - } - - var indexFileName = ConfigurationSettings.InputJson + JasixCommons.FileExt; - - ValidateIndexFile(indexFileName); - var writer = string.IsNullOrEmpty(ConfigurationSettings.OutputFile) - ? null : GZipUtilities.GetStreamWriter(ConfigurationSettings.OutputFile); - - using (var queryProcessor = new QueryProcessor(GZipUtilities.GetAppropriateStreamReader(ConfigurationSettings.InputJson), - FileUtilities.GetReadStream(indexFileName), writer)) - { - if (ConfigurationSettings.ListChromosomeName) - { - queryProcessor.PrintChromosomeList(); - return ExitCodes.Success; - } - - if (ConfigurationSettings.PrintHeaderOnly) - { - queryProcessor.PrintHeader(); - return ExitCodes.Success; - } - - if (ConfigurationSettings.Queries == null) - { - Console.WriteLine("Plese specify query region"); - return ExitCodes.BadArguments; - } - - queryProcessor.ProcessQuery(ConfigurationSettings.Queries, ConfigurationSettings.PrintHeader); - - } - return ExitCodes.Success; - } - - private static void ValidateIndexFile(string indexFileName) - { - if (!File.Exists(indexFileName)) - throw new UserErrorException("No index file found,please generate index file first."); - var indexFileCreateTime = File.GetCreationTime(indexFileName); - var fileCreateTime = File.GetCreationTime(ConfigurationSettings.InputJson); - if (fileCreateTime > indexFileCreateTime) - throw new UserErrorException("Index file is older than the input file, please re-generate the index."); - } + private static string _inputJson; + private static string _outputFile; + private static readonly List Queries = new List(); + private static bool _printHeader; + private static bool _printHeaderOnly; + private static bool _listChromosomeName; + private static bool _createIndex; public static int Main(string[] args) { @@ -75,44 +29,44 @@ public static int Main(string[] args) { "header|t", "print also the header lines", - v => ConfigurationSettings.PrintHeader = v != null + v => _printHeader = v != null }, { "only-header|H", "print only the header lines", - v => ConfigurationSettings.PrintHeaderOnly = v != null + v => _printHeaderOnly = v != null }, { "chromosomes|l", "list chromosome names", - v => ConfigurationSettings.ListChromosomeName = v != null + v => _listChromosomeName = v != null }, { "index|c", "create index", - v => ConfigurationSettings.CreateIndex = v != null + v => _createIndex = v != null }, { "in|i=", "input", - v => ConfigurationSettings.InputJson = v + v => _inputJson = v }, { "out|o=", "compressed output file name (default:console)", - v => ConfigurationSettings.OutputFile = v + v => _outputFile = v }, { "query|q=", "query range", - v => ConfigurationSettings.Queries.Add(v) + v => Queries.Add(v) } }; var exitCode = new ConsoleAppBuilder(args, ops) .Parse() - .CheckInputFilenameExists(ConfigurationSettings.InputJson, "input Json file", "[in.json.gz]") - .DisableOutput(!ConfigurationSettings.CreateIndex && ConfigurationSettings.OutputFile == null) + .CheckInputFilenameExists(_inputJson, "input Json file", "[in.json.gz]") + .DisableOutput(!_createIndex && _outputFile == null) .ShowBanner(Constants.Authors) .ShowHelpMenu("Indexes a Nirvana annotated JSON file", "-i in.json.gz [options]") .ShowErrors() @@ -120,6 +74,61 @@ public static int Main(string[] args) return (int)exitCode; } + + private static ExitCodes ProgramExecution() + { + if (_createIndex) + { + using (var indexCreator = new IndexCreator(_inputJson)) + { + indexCreator.CreateIndex(); + } + + return ExitCodes.Success; + } + + string indexFileName = _inputJson + JasixCommons.FileExt; + + ValidateIndexFile(indexFileName); + var writer = string.IsNullOrEmpty(_outputFile) + ? null : GZipUtilities.GetStreamWriter(_outputFile); + + using (var queryProcessor = new QueryProcessor(GZipUtilities.GetAppropriateStreamReader(_inputJson), + FileUtilities.GetReadStream(indexFileName), writer)) + { + if (_listChromosomeName) + { + queryProcessor.PrintChromosomeList(); + return ExitCodes.Success; + } + + if (_printHeaderOnly) + { + queryProcessor.PrintHeader(); + return ExitCodes.Success; + } + + if (Queries == null) + { + Console.WriteLine("Plese specify query region(s)"); + return ExitCodes.BadArguments; + } + + queryProcessor.ProcessQuery(Queries, _printHeader); + + } + return ExitCodes.Success; + } + + private static void ValidateIndexFile(string indexFileName) + { + if (!File.Exists(indexFileName)) + throw new UserErrorException("No index file found,please generate index file first."); + var indexFileCreateTime = File.GetCreationTime(indexFileName); + var fileCreateTime = File.GetCreationTime(_inputJson); + if (fileCreateTime > indexFileCreateTime) + throw new UserErrorException("Index file is older than the input file, please re-generate the index."); + } } } \ No newline at end of file diff --git a/Jasix/OnTheFlyIndexCreator.cs b/Jasix/OnTheFlyIndexCreator.cs index 380deb0a..26a2d1ae 100644 --- a/Jasix/OnTheFlyIndexCreator.cs +++ b/Jasix/OnTheFlyIndexCreator.cs @@ -29,7 +29,7 @@ public void SetHeader(string header) public void Add(IPosition position, long fileLocation) { - var chromName = position.VcfFields[VcfCommon.ChromIndex]; + var chromName = position.Chromosome.EnsemblName; var start = position.Start; var end = position.InfoData.End; @@ -49,7 +49,7 @@ public void Add(IPosition position, long fileLocation) end = Math.Max(position.RefAllele.Length - 1, altAlleleOffset) + start; } - _jasixIndex.Add(position.Chromosome.EnsemblName, start, end.Value, fileLocation); + _jasixIndex.Add(position.Chromosome.EnsemblName, start, end.Value, fileLocation, position.Chromosome.UcscName); } public void Dispose() diff --git a/Jasix/Properties/launchSettings.json b/Jasix/Properties/launchSettings.json index 9856b62a..f0218156 100644 --- a/Jasix/Properties/launchSettings.json +++ b/Jasix/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Jasix": { "commandName": "Project", - "commandLineArgs": " -i test.json.gz -c", + "commandLineArgs": " -i clinvar.json.gz -q chr1", "workingDirectory": "C:\\Development\\TestDatasets" } } diff --git a/Jasix/QueryProcessor.cs b/Jasix/QueryProcessor.cs index 9c736e95..384a62fb 100644 --- a/Jasix/QueryProcessor.cs +++ b/Jasix/QueryProcessor.cs @@ -99,7 +99,7 @@ public void ProcessQuery(IEnumerable queryStrings, bool printHeader = fa foreach (var queryString in queryStrings) { var query = Utilities.ParseQuery(queryString); - if (!_jasixIndex.ContainsChr(query.Item1)) continue; + if (!_jasixIndex.ContainsChr(query.Chromosome)) continue; var needComma = PrintLargeVariantsExtendingIntoQuery(query); PrintAllVariantsFromQueryBegin(query, needComma); } @@ -181,14 +181,16 @@ internal IEnumerable ReadOverlappingJsonLines((string Chr, int Start, in Console.WriteLine($"Error in line:\n{line}"); throw; } - - if (jsonEntry.chromosome != query.Item1) break; - if (jsonEntry.Start > query.Item3) break; + string indexChromName = _jasixIndex.GetIndexChromName(query.Chr); + string jsonChrom = _jasixIndex.GetIndexChromName(jsonEntry.chromosome); + if (jsonChrom != indexChromName) break; - if (!jsonEntry.Overlaps(query.Item2, query.Item3)) continue; + if (jsonEntry.Start > query.End) break; + + if (!jsonEntry.Overlaps(query.Start, query.End)) continue; // if there is an SV that starts before the query start that is printed by the large variant printer - if (Utilities.IsLargeVariant(jsonEntry.Start, jsonEntry.End) && jsonEntry.Start < query.Item2) continue; + if (Utilities.IsLargeVariant(jsonEntry.Start, jsonEntry.End) && jsonEntry.Start < query.Start) continue; yield return line; } } diff --git a/Nirvana/Properties/launchSettings.json b/Nirvana/Properties/launchSettings.json index 174f8b70..ddf8fef1 100644 --- a/Nirvana/Properties/launchSettings.json +++ b/Nirvana/Properties/launchSettings.json @@ -32,7 +32,7 @@ }, "RR clinvar": { "commandName": "Project", - "commandLineArgs": " --cache C:\\Development\\Cache\\26\\GRCh37\\Ensembl --sd C:\\Development\\SupplementaryDatabase\\43\\GRCh37 --ref C:\\Development\\References\\5\\Homo_sapiens.GRCh37.Nirvana.dat --in ClinVar20150901_ShankarBugNIR1202-ClinVar_dbSNP-unknown-WG-hg19.vcf.gz --out clinvar --disable-recomposition", + "commandLineArgs": " --cache C:\\Development\\Cache\\26\\GRCh37\\Ensembl --sd C:\\Development\\SupplementaryDatabase\\45\\GRCh37 --ref C:\\Development\\References\\5\\Homo_sapiens.GRCh37.Nirvana.dat --in ClinVar20150901_ShankarBugNIR1202-ClinVar_dbSNP-unknown-WG-hg19.vcf.gz --out clinvar --disable-recomposition", "workingDirectory": "c:\\Development\\TestDatasets" }, "RR dq": { diff --git a/UnitTests/Jasix/JasixQueryProcessingTests.cs b/UnitTests/Jasix/JasixQueryProcessingTests.cs index d5ea8738..c36e4d6a 100644 --- a/UnitTests/Jasix/JasixQueryProcessingTests.cs +++ b/UnitTests/Jasix/JasixQueryProcessingTests.cs @@ -143,6 +143,26 @@ public void TestQueryChr1() } } + [Fact] + public void Query_onthefly_Ensembl_and_Ucsc() + { + var readStream = new BlockGZipStream(ResourceUtilities.GetReadStream(Resources.TopPath("Clinvar20150901.json.gz")), CompressionMode.Decompress); + var indexStream = ResourceUtilities.GetReadStream(Resources.TopPath("Clinvar20150901.json.gz.jsi")); + + using (var qp = new QueryProcessor(new StreamReader(readStream), indexStream)) + { + var ucscResults = qp.ReadOverlappingJsonLines(Utilities.ParseQuery("chr1")); + var ensemblResults = qp.ReadOverlappingJsonLines(Utilities.ParseQuery("1")); + + int ucscCount = ucscResults.Count(); + int ensemblCount = ensemblResults.Count(); + + Assert.NotEqual(0,ucscCount); + Assert.NotEqual(0, ensemblCount); + Assert.Equal(ucscCount, ensemblCount); + } + } + [Fact] public void Report_overlapping_small_and_extending_large_variants() { diff --git a/UnitTests/Resources/Clinvar20150901.json.gz b/UnitTests/Resources/Clinvar20150901.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..a37c16456eddf910599be81cf1d3093139dd3e7e GIT binary patch literal 274829 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

g(cHNVji>7R8+sEy1^yO(leV< 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

u2xO5AUV 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 literal 0 HcmV?d00001 diff --git a/UnitTests/Resources/Clinvar20150901.json.gz.jsi b/UnitTests/Resources/Clinvar20150901.json.gz.jsi new file mode 100644 index 0000000000000000000000000000000000000000..8edf1788b5b16fee74801d7d0d471814c6905c92 GIT binary patch literal 2936 zcmcgueQXp(6z{YZ6XO?PO2in(H6dc|j-B26tp0Ph4c1uNP^=I&WN&YJx8`p5+}$fw zLIMREG<+k4q?GU_NFqp}SW}Qj0%!mwG1g*RK#1~@s$i%^u%d6~+S2P?6HSbNZ061E z?7ZLmz2AGYsC(>uAxf1nHHE_Yf}-n&t=I-kgz1`Dqv#5jT%ybG5(S5#npCkhL!YU| zDQu8L;&+N}CkbHUEtI7~BEzN#)r~j}S{99mVzsbs+Ki&8?1ML!8l`ciikcRjAryKY zLRhiy&4mDQNnV%R4O`iZN+W5i)Dj9`n=j}J_gGa@0WWI?Pk=AjUTUePmSDiZTZzsc z!@5ahRIzBWV&4NRJ4KJMz%g`SQB2dT6f^Bm7M4h)K*rHQTy0VstHp{6xGXIcqYj)% zhGLqE6;q9BH541w*rW-=v@K@ZQ3}%-C(Vj#2@5N6LyzESGOp;jMlrS2E3D{YoQMK* zc5Q-M4m?-0qu5F&*hw0EKx?!x)m1u5Si}i%hlf>OSyoz<7L}jVnnsmn#KFCa<4H(4 zu*XqwxHOEz)K;_@00v~5UTv6cFYHN$1CVTEAPiLjV5T(+G|3677zQmIs+^vlMB-uE zO3z{`L~Co+8k~#4H#R~RHERGC=S;V=dh;3AQLY5fg=Xg5!Y-(jT(9wv>qD_gq1g= zEgM_VQY0rh8>Sf|isAtK7OpUjH>ql;aaR##IgoLUN67#g94t{4oW_huk{9WS3gj|p zEVKH<3|=;9ta~^m-*9L%%PY!gI1LPI5^vKvo=ha8=qhyv_ljd$wGA>ipvU2)#Y&$S z4A#PyD;p%xynh5+tfzrg(=b5_y`r129+1%%y}XCJheuUBtt#_#56KJ(lEFF?v6^9v}`k}+DQ}AKh*ICwUSChO5USj^EkopFfsfqg}$tq5dbp+ z0rZbS0RO#MOXdaBJFn=HU77b3WC(z%hB??>*j>TC#fc$-XG@jrB;Mh4l$Vv3jm(u_ zl=xMg-cy4*F4sXD6c?o`fhFffeiJb;saDS{b|(~TmSMtALDojgOEkT$C!)16BfV`z zBP8<o`n6GJuFG-Sd}o(+!}a1sJN zR#@=H0*8<=ER7w@ejFZ{fCy?hb1m=MwjJ^Au{lUW?_cREXl*(5dhgjh)N{VO;O2$y zg0GsJm**oHHJ$i5e`Ck(@%t|=Zo5ARxzU!3&-X38HRVjp)}s&SAP>6Jd-K_&%T@`! zr?;^MFWR~(W)aIkNo<>PTX?jI{(0pjvxB{p^G2xefpEz z-z}R2M|e=@$~}+$ak8tRqi)GL0am=|;J$_EgLOYn`uy_Jb3)#ys}`aCs~4f)b{}h; zT=(%J^d~&;bsle=yatR5@HDSE(KtDm_|W>Rm-2cxA9||3Z|!miI(DRe{F1iz@f}y! zEqCM+KU#Ts{e*A)U*G9GHWmB?&|fXD^zORx_{opD*n%XYciIj-fp)aiEt6qN(DpN@ z9=*F^)0h5=7V_A&Ct#lG)%!ZZ>$3hYeojWrGj-*x*NgHUv=MJ{X7yN#F(n?ztfW=iHEi wU2eE@iJKc9;F23&ppY9rpoANK2%Z}Oh?*Ia2+?wbK%C6wEHsM^v(fCo0YAg Date: Mon, 23 Apr 2018 17:56:54 -0700 Subject: [PATCH 5/7] Feature/performance improvement 3185 (#173) * Bumped the version number and updated the codebase to use our optimized functions. * Refactored the compression library to eliminate dependencies to CommonUtilities and VariantAnnotation.Interface. Fixed ReSharper issues in CacheUtils. * Removed System.Memory from SAUtils. * Applied new ReSharper changes to the codebase. --- CacheUtils/.exclude | 0 .../CombineCacheDirectoriesMain.cs | 24 ++- .../CreateCache/CreateNirvanaDatabaseMain.cs | 2 +- CacheUtils/Commands/Download/ExternalFiles.cs | 3 +- .../ExtractTranscriptsMain.cs | 3 +- CacheUtils/Commands/Header/HeaderMain.cs | 19 +- .../RegulatoryRegionMerger.cs | 2 +- .../TranscriptMerger.cs | 2 +- .../RegulatoryGFF/CreateRegulatoryGffMain.cs | 4 +- .../DataStructures/Mutable/MutableGene.cs | 8 +- .../Mutable/MutableTranscript.cs | 2 +- .../DataDumperImport/IO/DataDumperReader.cs | 3 +- .../DataDumperImport/Import/Attribute.cs | 5 +- CacheUtils/Genbank/GenbankReader.cs | 5 +- CacheUtils/Genes/Combiners/CombinerUtils.cs | 6 +- CacheUtils/Genes/Combiners/HgncIdCombiner.cs | 2 +- .../Genes/Combiners/PartitionCombiner.cs | 6 +- CacheUtils/Genes/DataStores/GlobalCache.cs | 4 +- CacheUtils/Genes/DataStores/Hgnc.cs | 2 +- CacheUtils/Genes/GeneMerger.cs | 6 +- CacheUtils/Genes/IO/AssemblyReader.cs | 11 +- CacheUtils/Genes/IO/EnsemblGtfReader.cs | 7 +- CacheUtils/Genes/IO/GeneInfoReader.cs | 11 +- CacheUtils/Genes/IO/HgncReader.cs | 3 +- CacheUtils/Genes/IO/RefSeqGffReader.cs | 15 +- CacheUtils/Genes/IO/UgaGeneReader.cs | 27 +-- CacheUtils/Genes/UgaAssemblyCombiner.cs | 6 +- CacheUtils/IntermediateIO/CcdsReader.cs | 9 +- CacheUtils/IntermediateIO/GenbankReader.cs | 5 +- CacheUtils/IntermediateIO/GenbankWriter.cs | 10 +- .../IntermediateIO/IntermediateIoHeader.cs | 19 +- CacheUtils/IntermediateIO/LrgReader.cs | 9 +- .../IntermediateIO/MutableTranscriptReader.cs | 3 +- .../IntermediateIO/MutableTranscriptWriter.cs | 4 +- CacheUtils/IntermediateIO/PredictionReader.cs | 7 +- CacheUtils/IntermediateIO/PredictionWriter.cs | 2 +- .../IntermediateIO/RegulatoryRegionReader.cs | 15 +- CacheUtils/MiniCache/DataBundle.cs | 2 +- .../PredictionCache/PredictionCacheBuilder.cs | 15 +- .../PredictionCache/PredictionCacheStaging.cs | 8 +- .../PredictionCache/PredictionCacheWriter.cs | 10 +- .../TranscriptCache/TranscriptCacheBuilder.cs | 9 +- CacheUtils/Utilities/AccessionUtilities.cs | 2 +- CacheUtils/Utilities/HeaderUtilities.cs | 14 ++ CommandLine/Builders/ValidationExtensions.cs | 2 +- Compression/Compression.csproj | 2 - Compression/FileHandling/BgzipTextReader.cs | 31 ++- Compression/FileHandling/BlockStream.cs | 9 - Jasix/IndexCreator.cs | 3 +- Jasix/Jasix.csproj | 1 + Jasix/QueryProcessor.cs | 3 +- Nirvana.sln | 8 +- Nirvana/Nirvana.cs | 13 +- Nirvana/PluginUtilities.cs | 19 +- OptimizedCore/OptimizedCore.csproj | 14 ++ OptimizedCore/StringExtensions.cs | 99 +++++++++ Phantom/DataStructures/Genotype.cs | 9 +- Phantom/DataStructures/PositionSet.cs | 7 +- Phantom/Interfaces/ICodonInfoProvider.cs | 1 - Phantom/Interfaces/IPositionBuffer.cs | 3 +- Phantom/Interfaces/IVariantGenerator.cs | 1 - Phantom/Phantom.csproj | 1 + Phantom/Workers/PositionProcessor.cs | 2 - SAUtils/CreateOmimTsv/OmimPhenotype.cs | 9 +- SAUtils/CreateOmimTsv/OmimReader.cs | 7 +- SAUtils/DbSnpRemapper/ChromMapper.cs | 15 +- SAUtils/DbSnpRemapper/LeftoverMapper.cs | 3 +- SAUtils/DbSnpRemapper/Utilities.cs | 3 +- SAUtils/ExtractCosmicSvs/CosmicCnvReader.cs | 5 +- SAUtils/GeneScoresTsv/GeneScoreTsvCreator.cs | 5 +- .../InputFileParsers/ClinGen/ClinGenReader.cs | 9 +- .../Cosmic/MergedCosmicReader.cs | 23 +- .../CustomAnnotationReader.cs | 203 ++++++++---------- .../CustomInterval/CustomIntervalParser.cs | 138 ++++++------ SAUtils/InputFileParsers/DGV/DgvReader.cs | 3 +- .../DataSourceVersionReader.cs | 15 +- SAUtils/InputFileParsers/DbSnp/DbSnpReader.cs | 75 +++---- SAUtils/InputFileParsers/EVS/EvsReader.cs | 53 +++-- SAUtils/InputFileParsers/ExAc/ExAcReader.cs | 79 ++++--- SAUtils/InputFileParsers/GnomadReader.cs | 67 +++--- .../IntermediateAnnotation/GeneTsvReader.cs | 12 +- .../ParallelIntervalTsvReader.cs | 14 +- .../ParallelSaTsvReader.cs | 16 +- .../SaMiscellaniesReader.cs | 5 +- .../MitoMAP/MitoMapSvReader.cs | 7 +- .../MitoMAP/MitoMapVariantReader.cs | 9 +- .../InputFileParsers/OneKGen/OneKGenReader.cs | 65 +++--- .../OneKGen/oneKGenSvReader.cs | 5 +- .../InputFileParsers/TOPMed/TopMedReader.cs | 20 +- SAUtils/SAUtils.csproj | 3 - UnitTests/.exclude | 0 .../FileHandling/BlockStreamTests.cs | 64 ++++-- .../OptimizedCore/StringExtensionsTests.cs | 71 ++++++ .../IO/Caches/CacheHeaderTests.cs | 20 +- .../IO/Caches/TranscriptCacheReaderTests.cs | 11 +- UnitTests/Vcf/StringExtensionsTests.cs | 22 +- .../Vcf/VariantCreator/VariantFactoryTests.cs | 12 +- UnitTests/Vcf/VcfReaderTests.cs | 5 - VariantAnnotation.Interface/IO/IFileHeader.cs | 11 - .../Intervals/NullableInterval.cs | 14 -- .../Phantom/IRecomposer.cs | 1 - .../Positions/IPosition.cs | 4 +- .../AnnotatedSaDataSource.cs | 3 +- .../HgvsProteinNomenclature.cs | 13 +- .../Transcript/VariantEffect.cs | 5 +- .../Caches/TranscriptCacheData.cs | 5 +- VariantAnnotation/CommonAssemblyInfo.props | 6 +- VariantAnnotation/IO/Caches/CacheHeader.cs | 61 ++---- VariantAnnotation/IO/Caches/Header.cs | 49 +++++ .../IO/Caches/PredictionCacheCustomHeader.cs | 16 +- .../IO/Caches/PredictionCacheReader.cs | 48 +---- .../IO/Caches/PredictionHeader.cs | 55 +++++ .../IO/Caches/TranscriptCacheCustomHeader.cs | 9 +- .../IO/Caches/TranscriptCacheReader.cs | 2 +- .../IO/VcfWriter/VcfConversion.cs | 5 +- VariantAnnotation/PhyloP/PhylopWriter.cs | 7 +- .../Providers/RefMinorProvider.cs | 3 +- .../Providers/TranscriptAnnotationProvider.cs | 9 +- .../FullTranscriptAnnotator.cs | 3 +- VariantAnnotation/VariantAnnotation.csproj | 1 + Vcf/BreakEnd.cs | 4 +- Vcf/Info/VcfInfoParser.cs | 169 ++++++++------- Vcf/Position.cs | 31 +-- Vcf/ReadWriteUtilities.cs | 2 +- Vcf/Sample/AlleleDepths.cs | 22 +- Vcf/Sample/FailedFilter.cs | 5 +- Vcf/Sample/FormatIndices.cs | 15 +- Vcf/Sample/Genotype.cs | 6 +- Vcf/Sample/GenotypeQuality.cs | 46 ++-- Vcf/Sample/IntermediateSampleFields.cs | 31 +-- Vcf/Sample/ReadCounts.cs | 22 +- Vcf/Sample/SampleFieldExtractor.cs | 29 ++- Vcf/Sample/TotalDepth.cs | 42 +--- Vcf/Sample/VariantFrequency.cs | 29 +-- Vcf/SimplePosition.cs | 7 +- Vcf/StringExtensions.cs | 25 ++- Vcf/VariantCreator/CnvCreator.cs | 36 ++-- Vcf/VariantCreator/ReferenceVariantCreator.cs | 6 +- Vcf/VariantCreator/RepeatExpansionCreator.cs | 14 +- Vcf/VariantCreator/SmallVariantCreator.cs | 24 +-- .../StructuralVariantCreator.cs | 6 +- Vcf/VariantCreator/VariantFactory.cs | 28 +-- Vcf/Vcf.csproj | 1 + Vcf/VcfReader.cs | 15 +- Vcf/VcfReaderUtils.cs | 2 +- 145 files changed, 1387 insertions(+), 1192 deletions(-) delete mode 100644 CacheUtils/.exclude create mode 100644 CacheUtils/Utilities/HeaderUtilities.cs create mode 100644 OptimizedCore/OptimizedCore.csproj create mode 100644 OptimizedCore/StringExtensions.cs delete mode 100644 UnitTests/.exclude create mode 100644 UnitTests/OptimizedCore/StringExtensionsTests.cs delete mode 100644 VariantAnnotation.Interface/IO/IFileHeader.cs delete mode 100644 VariantAnnotation.Interface/Intervals/NullableInterval.cs create mode 100644 VariantAnnotation/IO/Caches/Header.cs create mode 100644 VariantAnnotation/IO/Caches/PredictionHeader.cs diff --git a/CacheUtils/.exclude b/CacheUtils/.exclude deleted file mode 100644 index e69de29b..00000000 diff --git a/CacheUtils/Commands/CombineCacheDirectories/CombineCacheDirectoriesMain.cs b/CacheUtils/Commands/CombineCacheDirectories/CombineCacheDirectoriesMain.cs index c63e199e..3ad9adbb 100644 --- a/CacheUtils/Commands/CombineCacheDirectories/CombineCacheDirectoriesMain.cs +++ b/CacheUtils/Commands/CombineCacheDirectories/CombineCacheDirectoriesMain.cs @@ -47,8 +47,8 @@ private static ExitCodes ProgramExecution() var siftPredictionsPerRef = new Prediction[numRefSeqs][]; var polyphenPredictionsPerRef = new Prediction[numRefSeqs][]; - PredictionCacheReader.PredictionHeader siftHeader; - PredictionCacheReader.PredictionHeader polyphenHeader; + PredictionHeader siftHeader; + PredictionHeader polyphenHeader; using (var siftReader = new PredictionCacheReader(FileUtilities.GetReadStream(CacheConstants.SiftPath(_inputPrefix)), PredictionCacheReader.SiftDescriptions)) using (var siftReader2 = new PredictionCacheReader(FileUtilities.GetReadStream(CacheConstants.SiftPath(_inputPrefix2)), PredictionCacheReader.SiftDescriptions)) @@ -83,7 +83,7 @@ private static ExitCodes ProgramExecution() logger.WriteLine(); WritePredictions(logger, "SIFT", CacheConstants.SiftPath(_outputPrefix), siftHeader, siftPredictionsPerRef); WritePredictions(logger, "PolyPhen", CacheConstants.PolyPhenPath(_outputPrefix), polyphenHeader, polyphenPredictionsPerRef); - WriteTranscripts(logger, GetHeader(caches.Cache.Header), combinedIntervalArrays, + WriteTranscripts(logger, CloneHeader(caches.Cache.Header), combinedIntervalArrays, caches.Cache.RegulatoryRegionIntervalArrays); return ExitCodes.Success; @@ -101,14 +101,14 @@ private static void WriteTranscripts(ILogger logger, CacheHeader header, } private static void WritePredictions(ILogger logger, string description, string filePath, - PredictionCacheReader.PredictionHeader header, Prediction[][] predictionsPerRef) + PredictionHeader header, Prediction[][] predictionsPerRef) { logger.Write($"- writing {description} predictions... "); using (var stream = new BlockStream(new Zstandard(), FileUtilities.GetCreateStream(filePath), CompressionMode.Compress)) - using (var writer = new PredictionCacheWriter(stream, GetHeader(header.Header))) + using (var writer = new PredictionCacheWriter(stream, CloneHeader(header))) { - writer.Write(header.Lut, predictionsPerRef); + writer.Write(header.LookupTable, predictionsPerRef); } logger.WriteLine("finished."); @@ -161,9 +161,15 @@ private static Interval GetUpdatedTranscript(Interval return new Interval(transcript.Start, transcript.End, updatedTranscript); } - private static CacheHeader GetHeader(CacheHeader header) => new CacheHeader(CacheConstants.Identifier, - header.SchemaVersion, header.DataVersion, Source.BothRefSeqAndEnsembl, DateTime.Now.Ticks, - header.GenomeAssembly, header.CustomHeader); + private static VariantAnnotation.IO.Caches.Header CloneBaseHeader(VariantAnnotation.IO.Caches.Header header) => + new VariantAnnotation.IO.Caches.Header(CacheConstants.Identifier, header.SchemaVersion, header.DataVersion, + Source.BothRefSeqAndEnsembl, DateTime.Now.Ticks, header.GenomeAssembly); + + private static PredictionHeader CloneHeader(PredictionHeader header) => + new PredictionHeader(CloneBaseHeader(header), header.Custom, header.LookupTable); + + private static CacheHeader CloneHeader(CacheHeader header) => + new CacheHeader(CloneBaseHeader(header), header.Custom); private static (Prediction[] Predictions, int Offset) CombinePredictions(ILogger logger, IChromosome chromosome, string description, PredictionCacheReader reader, PredictionCacheReader reader2) diff --git a/CacheUtils/Commands/CreateCache/CreateNirvanaDatabaseMain.cs b/CacheUtils/Commands/CreateCache/CreateNirvanaDatabaseMain.cs index dc7209f4..44a406e9 100644 --- a/CacheUtils/Commands/CreateCache/CreateNirvanaDatabaseMain.cs +++ b/CacheUtils/Commands/CreateCache/CreateNirvanaDatabaseMain.cs @@ -39,7 +39,7 @@ private static ExitCodes ProgramExecution() string polyphenPath = _inputPrefix + ".polyphen.gz"; string regulatoryPath = _inputPrefix + ".regulatory.gz"; - var (refIndexToChromosome, refNameToChromosome, numRefSeqs) = SequenceHelper.GetDictionaries(_inputReferencePath); + (var refIndexToChromosome, var refNameToChromosome, int numRefSeqs) = SequenceHelper.GetDictionaries(_inputReferencePath); using (var transcriptReader = new MutableTranscriptReader(GZipUtilities.GetAppropriateReadStream(transcriptPath), refIndexToChromosome)) using (var regulatoryReader = new RegulatoryRegionReader(GZipUtilities.GetAppropriateReadStream(regulatoryPath), refIndexToChromosome)) diff --git a/CacheUtils/Commands/Download/ExternalFiles.cs b/CacheUtils/Commands/Download/ExternalFiles.cs index 283a6a6d..539531a5 100644 --- a/CacheUtils/Commands/Download/ExternalFiles.cs +++ b/CacheUtils/Commands/Download/ExternalFiles.cs @@ -6,6 +6,7 @@ using CacheUtils.IntermediateIO; using CacheUtils.Utilities; using Compression.Utilities; +using OptimizedCore; using VariantAnnotation.Interface; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.Sequence; @@ -100,7 +101,7 @@ private static int GetNumGenbankFiles(ILogger logger) string line = reader.ReadLine(); if (line == null) break; - string filename = line.Split('\t')[1]; + string filename = line.OptimizedSplit('\t')[1]; if (!filename.EndsWith(".rna.gbff.gz")) continue; int num = int.Parse(filename.Substring(6, filename.Length - 18)); diff --git a/CacheUtils/Commands/ExtractTranscripts/ExtractTranscriptsMain.cs b/CacheUtils/Commands/ExtractTranscripts/ExtractTranscriptsMain.cs index fd36a288..bc7260d6 100644 --- a/CacheUtils/Commands/ExtractTranscripts/ExtractTranscriptsMain.cs +++ b/CacheUtils/Commands/ExtractTranscripts/ExtractTranscriptsMain.cs @@ -114,9 +114,8 @@ private static (PredictionCacheStaging Staging, Prediction[] Predictions) GetPre logger.Write($"- retrieving {description} predictions... "); var indexSet = GetUniqueIndices(transcripts, indexFunc); - var lut = reader.Header.Lut; var predictionsPerRef = GetPredictions(indexSet, chromosome, numRefSeqs, oldPredictions); - var staging = new PredictionCacheStaging(reader.Header.Header, lut, predictionsPerRef); + var staging = new PredictionCacheStaging(reader.Header, predictionsPerRef); logger.WriteLine($"found {indexSet.Count} predictions."); return (staging, predictionsPerRef[chromosome.Index]); diff --git a/CacheUtils/Commands/Header/HeaderMain.cs b/CacheUtils/Commands/Header/HeaderMain.cs index 6a1d0a10..254c617c 100644 --- a/CacheUtils/Commands/Header/HeaderMain.cs +++ b/CacheUtils/Commands/Header/HeaderMain.cs @@ -1,9 +1,6 @@ using System; -using System.IO.Compression; using CommandLine.Builders; using CommandLine.NDesk.Options; -using Compression.Algorithms; -using Compression.FileHandling; using ErrorHandling; using ErrorHandling.Exceptions; using VariantAnnotation.IO.Caches; @@ -18,8 +15,8 @@ public static class HeaderMain private static ExitCodes ProgramExecution() { - var cachePath = CacheConstants.TranscriptPath(_inputPrefix); - var header = GetHeaderInformation(cachePath); + string cachePath = CacheConstants.TranscriptPath(_inputPrefix); + var header = GetHeaderInformation(cachePath); Console.WriteLine($"Versions: Schema: {header.Schema}, Data: {header.Data}, VEP: {header.Vep}"); return ExitCodes.Success; @@ -28,18 +25,14 @@ private static ExitCodes ProgramExecution() private static (ushort Schema, ushort Data, ushort Vep) GetHeaderInformation(string cachePath) { CacheHeader header; - TranscriptCacheCustomHeader customHeader = null; - - using (var stream = FileUtilities.GetReadStream(cachePath)) - using (var blockStream = new BlockStream(new Zstandard(), stream, CompressionMode.Decompress)) + using (var stream = FileUtilities.GetReadStream(cachePath)) { - header = blockStream.ReadHeader(CacheHeader.Read, TranscriptCacheCustomHeader.Read) as CacheHeader; - if (header != null) customHeader = header.CustomHeader as TranscriptCacheCustomHeader; + header = CacheHeader.Read(stream); } - if (header == null || customHeader == null) throw new InvalidFileFormatException($"Could not parse the header information correctly for {cachePath}"); + if (header == null) throw new InvalidFileFormatException($"Could not parse the header information correctly for {cachePath}"); - return (header.SchemaVersion, header.DataVersion, customHeader.VepVersion); + return (header.SchemaVersion, header.DataVersion, header.Custom.VepVersion); } public static ExitCodes Run(string command, string[] args) diff --git a/CacheUtils/Commands/ParseVepCacheDirectory/RegulatoryRegionMerger.cs b/CacheUtils/Commands/ParseVepCacheDirectory/RegulatoryRegionMerger.cs index 7e68eea8..400437a5 100644 --- a/CacheUtils/Commands/ParseVepCacheDirectory/RegulatoryRegionMerger.cs +++ b/CacheUtils/Commands/ParseVepCacheDirectory/RegulatoryRegionMerger.cs @@ -18,7 +18,7 @@ public static IEnumerable Merge(IEnumerable Merge(ILogger logger, IEnumerable transcripts, Dictionary idToGenbankEntry) { - var transcriptId = transcripts[0].Id; + string transcriptId = transcripts[0].Id; if (transcripts.Count == 1) { diff --git a/CacheUtils/Commands/RegulatoryGFF/CreateRegulatoryGffMain.cs b/CacheUtils/Commands/RegulatoryGFF/CreateRegulatoryGffMain.cs index 1c143bdf..6107e977 100644 --- a/CacheUtils/Commands/RegulatoryGFF/CreateRegulatoryGffMain.cs +++ b/CacheUtils/Commands/RegulatoryGFF/CreateRegulatoryGffMain.cs @@ -21,7 +21,7 @@ private static ExitCodes ProgramExecution() { using (var writer = GZipUtilities.GetStreamWriter(_outputFileName)) { - var cachePath = CacheConstants.TranscriptPath(_inputPrefix); + string cachePath = CacheConstants.TranscriptPath(_inputPrefix); var sequenceData = SequenceHelper.GetDictionaries(_referencePath); // load the cache @@ -62,7 +62,7 @@ public static ExitCodes Run(string command, string[] args) } }; - var commandLineExample = $"{command} --in --out "; + string commandLineExample = $"{command} --in --out "; return new ConsoleAppBuilder(args, ops) .UseVersionProvider(new VersionProvider()) diff --git a/CacheUtils/DataDumperImport/DataStructures/Mutable/MutableGene.cs b/CacheUtils/DataDumperImport/DataStructures/Mutable/MutableGene.cs index c4bb2528..cd49aa89 100644 --- a/CacheUtils/DataDumperImport/DataStructures/Mutable/MutableGene.cs +++ b/CacheUtils/DataDumperImport/DataStructures/Mutable/MutableGene.cs @@ -31,7 +31,7 @@ public MutableGene(IChromosome chromosome, int start, int end, bool onReverseStr public override string ToString() { - var strand = OnReverseStrand ? "R" : "F"; + string strand = OnReverseStrand ? "R" : "F"; return $"{GeneId}: {Chromosome.UcscName} {Start}-{End} {strand} symbol: {Symbol} ({SymbolSource}), HGNC ID: {HgncId}"; } @@ -53,7 +53,7 @@ public override int GetHashCode() unchecked { // ReSharper disable NonReadonlyMemberInGetHashCode - var hashCode = Chromosome.Index.GetHashCode(); + int hashCode = Chromosome.Index.GetHashCode(); hashCode = (hashCode * 397) ^ Start; hashCode = (hashCode * 397) ^ End; hashCode = (hashCode * 397) ^ OnReverseStrand.GetHashCode(); @@ -69,10 +69,10 @@ public override int GetHashCode() public UgaGene ToUgaGene(bool isGrch37) { - var (ensemblGeneId, entrezGeneId) = GeneId.StartsWith("ENSG") ? (GeneId, null as string) : (null as string, GeneId); + (string ensemblGeneId, string entrezGeneId) = GeneId.StartsWith("ENSG") ? (GeneId, null as string) : (null as string, GeneId); IInterval interval = new Interval(Start, End); - var (grch37, grch38) = isGrch37 ? (interval, null as IInterval) : (null as IInterval, interval); + (IInterval grch37, IInterval grch38) = isGrch37 ? (interval, null as IInterval) : (null as IInterval, interval); return new UgaGene(Chromosome, grch37, grch38, OnReverseStrand, entrezGeneId, ensemblGeneId, Symbol, HgncId); diff --git a/CacheUtils/DataDumperImport/DataStructures/Mutable/MutableTranscript.cs b/CacheUtils/DataDumperImport/DataStructures/Mutable/MutableTranscript.cs index 8ef84c96..984e53c5 100644 --- a/CacheUtils/DataDumperImport/DataStructures/Mutable/MutableTranscript.cs +++ b/CacheUtils/DataDumperImport/DataStructures/Mutable/MutableTranscript.cs @@ -107,7 +107,7 @@ public override int GetHashCode() unchecked { // ReSharper disable NonReadonlyMemberInGetHashCode - var hashCode = Chromosome.Index.GetHashCode(); + int hashCode = Chromosome.Index.GetHashCode(); hashCode = (hashCode * 397) ^ Start; hashCode = (hashCode * 397) ^ End; hashCode = (hashCode * 397) ^ Id.GetHashCode(); diff --git a/CacheUtils/DataDumperImport/IO/DataDumperReader.cs b/CacheUtils/DataDumperImport/IO/DataDumperReader.cs index da325b88..4f9e6fbf 100644 --- a/CacheUtils/DataDumperImport/IO/DataDumperReader.cs +++ b/CacheUtils/DataDumperImport/IO/DataDumperReader.cs @@ -4,6 +4,7 @@ using System.Text; using CacheUtils.DataDumperImport.DataStructures.Import; using CacheUtils.DataDumperImport.FauxRegex; +using OptimizedCore; namespace CacheUtils.DataDumperImport.IO { @@ -65,7 +66,7 @@ private StringKeyValueNode GetMultiLineKeyValue(string key, string value) while (true) { string line = GetNextLine().Trim(); - if (line.StartsWith("\'")) break; + if (line.OptimizedStartsWith('\'')) break; _sb.Append(' '); _sb.Append(line); } diff --git a/CacheUtils/DataDumperImport/Import/Attribute.cs b/CacheUtils/DataDumperImport/Import/Attribute.cs index 38d6b667..e7f8522e 100644 --- a/CacheUtils/DataDumperImport/Import/Attribute.cs +++ b/CacheUtils/DataDumperImport/Import/Attribute.cs @@ -3,6 +3,7 @@ using System.Text.RegularExpressions; using CacheUtils.DataDumperImport.DataStructures.Import; using CacheUtils.DataDumperImport.Utilities; +using OptimizedCore; using VariantAnnotation.Caches.DataStructures; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.Intervals; @@ -46,7 +47,7 @@ public static (IInterval[] MicroRnas, IRnaEdit[] RnaEdits, bool CdsStartNotFound if (!(node is ObjectValueNode objectValue)) throw new InvalidDataException($"Could not transform the AbstractData object into an ObjectValue: [{node.GetType()}]"); - (var key, var value) = ParseKeyValue(objectValue); + (string key, string value) = ParseKeyValue(objectValue); if (key == null) continue; // ReSharper disable once SwitchStatementMissingSomeCases @@ -85,7 +86,7 @@ private static IInterval GetInterval(string s) private static RnaEdit GetRnaEdit(string s) { - var cols = s.Split(' '); + var cols = s.OptimizedSplit(' '); if (cols.Length != 3) throw new InvalidDataException($"Expected 3 columns but found {cols.Length} when parsing RNA edit"); int start = int.Parse(cols[0]); diff --git a/CacheUtils/Genbank/GenbankReader.cs b/CacheUtils/Genbank/GenbankReader.cs index d7c4f9e1..cdf129c5 100644 --- a/CacheUtils/Genbank/GenbankReader.cs +++ b/CacheUtils/Genbank/GenbankReader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using OptimizedCore; using VariantAnnotation.Interface.Intervals; using VariantAnnotation.Utilities; @@ -34,7 +35,7 @@ public GenbankEntry GetGenbankEntry() // assert that the record starts with LOCUS if (!HasLocus()) return null; - var (transcriptId, transcriptVersion) = ParseHeader(); + (string transcriptId, byte transcriptVersion) = ParseHeader(); var featureData = ParseFeatures(); ParseOrigin(); @@ -150,7 +151,7 @@ private static IInterval GetInterval(string info) private static IInterval GetJoinInterval(string info) { - var cols = info.Substring(5, info.Length - 6).Split(','); + var cols = info.Substring(5, info.Length - 6).OptimizedSplit(','); int start = int.Parse(cols[0].Split("..")[0]); int end = int.Parse(cols[1].Split("..")[1]); return new Interval(start, end); diff --git a/CacheUtils/Genes/Combiners/CombinerUtils.cs b/CacheUtils/Genes/Combiners/CombinerUtils.cs index 2b208578..d815dfc4 100644 --- a/CacheUtils/Genes/Combiners/CombinerUtils.cs +++ b/CacheUtils/Genes/Combiners/CombinerUtils.cs @@ -8,9 +8,9 @@ public static class CombinerUtils { public static UgaGene Merge(UgaGene gene37, UgaGene gene38) { - var ensemblId = CombineField(gene37.EnsemblId, gene38.EnsemblId); - var entrezGeneId = CombineField(gene37.EntrezGeneId, gene38.EntrezGeneId); - var hgncId = CombineField(gene37.HgncId, gene38.HgncId); + string ensemblId = CombineField(gene37.EnsemblId, gene38.EnsemblId); + string entrezGeneId = CombineField(gene37.EntrezGeneId, gene38.EntrezGeneId); + int hgncId = CombineField(gene37.HgncId, gene38.HgncId); return new UgaGene(gene37.Chromosome, gene37.GRCh37, gene38.GRCh38, gene37.OnReverseStrand, entrezGeneId, ensemblId, gene37.Symbol, hgncId); } diff --git a/CacheUtils/Genes/Combiners/HgncIdCombiner.cs b/CacheUtils/Genes/Combiners/HgncIdCombiner.cs index 3b895195..3e374491 100644 --- a/CacheUtils/Genes/Combiners/HgncIdCombiner.cs +++ b/CacheUtils/Genes/Combiners/HgncIdCombiner.cs @@ -13,7 +13,7 @@ public void Combine(List combinedGenes, HashSet remainingGenes var genesByHgnc37 = remainingGenes37.GetMultiValueDict(x => x.HgncId); var genesByHgnc38 = remainingGenes38.GetMultiValueDict(x => x.HgncId); - foreach (var hgncId in hgncIds) + foreach (int hgncId in hgncIds) { var genes37 = GetGenesByHgncId(genesByHgnc37, hgncId); var genes38 = GetGenesByHgncId(genesByHgnc38, hgncId); diff --git a/CacheUtils/Genes/Combiners/PartitionCombiner.cs b/CacheUtils/Genes/Combiners/PartitionCombiner.cs index 47870385..472bf72f 100644 --- a/CacheUtils/Genes/Combiners/PartitionCombiner.cs +++ b/CacheUtils/Genes/Combiners/PartitionCombiner.cs @@ -24,7 +24,7 @@ private static void CombineSet(ICollection combinedGenes, IEnumerable GetGenesByKey(IReadOnlyDictionary GetAllKeys(IEnumerable keys37, IEnumerable keys38) { var keys = new HashSet(); - foreach (var key in keys37) keys.Add(key); - foreach (var key in keys38) keys.Add(key); + foreach (string key in keys37) keys.Add(key); + foreach (string key in keys38) keys.Add(key); return keys; } diff --git a/CacheUtils/Genes/DataStores/GlobalCache.cs b/CacheUtils/Genes/DataStores/GlobalCache.cs index 2229a6ce..a438bc6d 100644 --- a/CacheUtils/Genes/DataStores/GlobalCache.cs +++ b/CacheUtils/Genes/DataStores/GlobalCache.cs @@ -55,8 +55,8 @@ private static IEnumerable LoadGenes(Stream stream, foreach (var transcript in transcripts) { - var gene = transcript.Gene; - var key = GetGeneKey(gene); + var gene = transcript.Gene; + string key = GetGeneKey(gene); if (geneDict.ContainsKey(key)) continue; gene.Chromosome = refNameToChromosome38[gene.Chromosome.UcscName]; diff --git a/CacheUtils/Genes/DataStores/Hgnc.cs b/CacheUtils/Genes/DataStores/Hgnc.cs index cde8eee8..7b8b2f1f 100644 --- a/CacheUtils/Genes/DataStores/Hgnc.cs +++ b/CacheUtils/Genes/DataStores/Hgnc.cs @@ -40,7 +40,7 @@ public int AddCoordinates(EnsemblGtf ensemblGtf, RefSeqGff refSeqGff) { foreach (var hgncGene in HgncGenes) { - var (refSeqGenes, ensemblGene, numMatches) = GetGenes(hgncGene.EntrezGeneId, + (var refSeqGenes, EnsemblGene ensemblGene, int numMatches) = GetGenes(hgncGene.EntrezGeneId, refSeqGff.EntrezGeneIdToGene, hgncGene.EnsemblId, ensemblGtf.EnsemblIdToGene); switch (numMatches) diff --git a/CacheUtils/Genes/GeneMerger.cs b/CacheUtils/Genes/GeneMerger.cs index e83e240e..294f6e4a 100644 --- a/CacheUtils/Genes/GeneMerger.cs +++ b/CacheUtils/Genes/GeneMerger.cs @@ -27,7 +27,7 @@ public static Dictionary> MergeByHgnc(this IUpdateHgncData foreach (var kvp in genesByRef) { var hgncIdToGenes = kvp.Value.GetMultiValueDict(x => x.HgncId.ToString() + '|' + (x.OnReverseStrand ? 'R' : 'F')); - var (mergedGenes, numOrphanEntries, numMergedEntries) = GetMergedGenes(hgncIdToGenes, isGrch37); + (var mergedGenes, int numOrphanEntries, int numMergedEntries) = GetMergedGenes(hgncIdToGenes, isGrch37); mergedGenesByRef[kvp.Key] = mergedGenes; @@ -99,13 +99,13 @@ private static List ConvertToUgaGenes(IEnumerable genes, b private static UgaGene GetMergedGene(MutableGene geneA, MutableGene geneB, bool isGrch37) { - var (ensemblGene, refSeqGene) = geneA.GeneId.StartsWith("ENSG") ? (geneA, geneB) : (geneB, geneA); + (MutableGene ensemblGene, MutableGene refSeqGene) = geneA.GeneId.StartsWith("ENSG") ? (geneA, geneB) : (geneB, geneA); if (ensemblGene.Chromosome.Index != refSeqGene.Chromosome.Index) throw new InvalidDataException($"The two genes are on different chromosomes: {geneA.GeneId} & {geneB.GeneId}"); if (ensemblGene.OnReverseStrand != refSeqGene.OnReverseStrand) throw new InvalidDataException($"Both genes do not have the same orientation: {geneA.GeneId} & {geneB.GeneId}"); IInterval interval = GetMergedInterval(ensemblGene, refSeqGene); - var (grch37, grch38) = isGrch37 ? (interval, null as IInterval) : (null as IInterval, interval); + (IInterval grch37, IInterval grch38) = isGrch37 ? (interval, null as IInterval) : (null as IInterval, interval); return new UgaGene(ensemblGene.Chromosome, grch37, grch38, ensemblGene.OnReverseStrand, refSeqGene.GeneId, ensemblGene.GeneId, ensemblGene.Symbol, ensemblGene.HgncId); diff --git a/CacheUtils/Genes/IO/AssemblyReader.cs b/CacheUtils/Genes/IO/AssemblyReader.cs index 4a1ae37a..b390932b 100644 --- a/CacheUtils/Genes/IO/AssemblyReader.cs +++ b/CacheUtils/Genes/IO/AssemblyReader.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using OptimizedCore; using VariantAnnotation.Interface.Sequence; namespace CacheUtils.Genes.IO @@ -16,14 +17,14 @@ public static IDictionary GetAccessionToChromosome(StreamRe while (true) { - var line = reader.ReadLine(); + string line = reader.ReadLine(); if (line == null) break; - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; - var cols = line.Split('\t'); - var accession = cols[AccessionIndex]; - var ucscName = cols[UcscIndex]; + var cols = line.OptimizedSplit('\t'); + string accession = cols[AccessionIndex]; + string ucscName = cols[UcscIndex]; if (!refNameToChromosome.TryGetValue(ucscName, out IChromosome chromosome)) continue; accessionToChromosome[accession] = chromosome; diff --git a/CacheUtils/Genes/IO/EnsemblGtfReader.cs b/CacheUtils/Genes/IO/EnsemblGtfReader.cs index 74c6959f..7292f7b6 100644 --- a/CacheUtils/Genes/IO/EnsemblGtfReader.cs +++ b/CacheUtils/Genes/IO/EnsemblGtfReader.cs @@ -3,6 +3,7 @@ using System.IO; using CacheUtils.Genes.DataStructures; using ErrorHandling.Exceptions; +using OptimizedCore; using VariantAnnotation.Interface.Sequence; namespace CacheUtils.Genes.IO @@ -34,9 +35,9 @@ public EnsemblGene[] GetGenes() string line = _reader.ReadLine(); if (line == null) break; - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols.Length != 9) throw new InvalidDataException($"Expected 9 columns but found {cols.Length} when parsing the GFF entry."); string featureType = cols[FeatureTypeIndex]; @@ -79,7 +80,7 @@ private static (string EnsemblGeneId, string Name) GetGffFields(string[] cols) foreach (string col in cols) { - var kvp = col.Trim().Split(' '); + var kvp = col.Trim().OptimizedSplit(' '); string key = kvp[0]; string value = kvp[1].Trim('\"'); diff --git a/CacheUtils/Genes/IO/GeneInfoReader.cs b/CacheUtils/Genes/IO/GeneInfoReader.cs index ea3a39de..8d67079e 100644 --- a/CacheUtils/Genes/IO/GeneInfoReader.cs +++ b/CacheUtils/Genes/IO/GeneInfoReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using CacheUtils.Genes.DataStructures; +using OptimizedCore; namespace CacheUtils.Genes.IO { @@ -22,11 +23,11 @@ public GeneInfoReader(StreamReader reader) private void SetColumnIndices(string line) { - if (line.StartsWith("#Format: ")) line = line.Substring(9); - if (line.StartsWith("#")) line = line.Substring(1); + if (line.StartsWith("#Format: ")) line = line.Substring(9); + if (line.OptimizedStartsWith('#')) line = line.Substring(1); - var cols = line.Split('\t'); - if (cols.Length == 1) cols = line.Split(' '); + var cols = line.OptimizedSplit('\t'); + if (cols.Length == 1) cols = line.OptimizedSplit(' '); for (var index = 0; index < cols.Length; index++) { @@ -67,7 +68,7 @@ private GeneInfo Next() if (!line.StartsWith("9606")) return null; - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols.Length != 16) throw new InvalidDataException($"Expected 16 columns but found {cols.Length} when parsing the gene entry:\n[{line}]"); try diff --git a/CacheUtils/Genes/IO/HgncReader.cs b/CacheUtils/Genes/IO/HgncReader.cs index 56928528..e8acd9e2 100644 --- a/CacheUtils/Genes/IO/HgncReader.cs +++ b/CacheUtils/Genes/IO/HgncReader.cs @@ -3,6 +3,7 @@ using System.IO; using CacheUtils.Genes.DataStructures; using CommonUtilities; +using OptimizedCore; using VariantAnnotation.Interface.Sequence; namespace CacheUtils.Genes.IO @@ -33,7 +34,7 @@ private HgncGene Next() string line = _reader.ReadLine(); if (line == null) return null; - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols.Length != 49) throw new InvalidDataException($"Expected 48 columns but found {cols.Length} when parsing the gene entry:[{line}]"); try diff --git a/CacheUtils/Genes/IO/RefSeqGffReader.cs b/CacheUtils/Genes/IO/RefSeqGffReader.cs index 05b68445..6ef8fec1 100644 --- a/CacheUtils/Genes/IO/RefSeqGffReader.cs +++ b/CacheUtils/Genes/IO/RefSeqGffReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using CacheUtils.Genes.DataStructures; +using OptimizedCore; using VariantAnnotation.Interface.Sequence; namespace CacheUtils.Genes.IO @@ -32,9 +33,9 @@ public void AddGenes(List refSeqGenes) string line = _reader.ReadLine(); if (line == null) break; - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols.Length != 9) throw new InvalidDataException($"Expected 9 columns but found {cols.Length} when parsing the GFF entry."); string featureType = cols[FeatureTypeIndex]; @@ -52,7 +53,7 @@ private void AddGene(string[] cols, ICollection refSeqGenes) int start = int.Parse(cols[StartIndex]); int end = int.Parse(cols[EndIndex]); bool onReverseStrand = cols[StrandIndex] == "-"; - var infoCols = cols[InfoIndex].Split(';'); + var infoCols = cols[InfoIndex].OptimizedSplit(';'); var info = GetGffFields(infoCols); var gene = new RefSeqGene(chromosome, start, end, onReverseStrand, info.EntrezGeneId, info.Name, info.HgncId); @@ -82,15 +83,13 @@ private static (string Name, string EntrezGeneId, int HgncId) foreach (string col in cols) { - var kvp = col.Split('='); - string key = kvp[0]; - string value = kvp[1]; + (string key, string value) = col.OptimizedKeyValue(); // ReSharper disable once SwitchStatementMissingSomeCases switch (key) { case "Dbxref": - var ids = value.Split(','); + var ids = value.OptimizedSplit(','); (entrezGeneId, hgncId) = GetIds(ids); break; case "Name": @@ -109,7 +108,7 @@ private static (string EntrezGeneId, int HgncId) GetIds(IEnumerable ids) foreach (string idPair in ids) { - var cols = idPair.Split(':'); + var cols = idPair.OptimizedSplit(':'); // ReSharper disable once SwitchStatementMissingSomeCases switch (cols[0]) diff --git a/CacheUtils/Genes/IO/UgaGeneReader.cs b/CacheUtils/Genes/IO/UgaGeneReader.cs index fb05ec99..0351d08f 100644 --- a/CacheUtils/Genes/IO/UgaGeneReader.cs +++ b/CacheUtils/Genes/IO/UgaGeneReader.cs @@ -4,6 +4,7 @@ using System.Text; using CacheUtils.Genes.DataStructures; using CommonUtilities; +using OptimizedCore; using VariantAnnotation.Interface.Intervals; using VariantAnnotation.Interface.Sequence; @@ -39,23 +40,23 @@ public UgaGene[] GetGenes() private UgaGene GetNextGene() { - var line = _reader.ReadLine(); + string line = _reader.ReadLine(); if (line == null) return null; - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols.Length != 11) throw new InvalidDataException($"Expected 11 columns, but found {cols.Length} columns."); - var ucscRefName = cols[0]; - var chromosome = ReferenceNameUtilities.GetChromosome(_refNameToChromosome, ucscRefName); - var symbol = cols[2]; - var start37 = int.Parse(cols[3]); - var end37 = int.Parse(cols[4]); - var start38 = int.Parse(cols[5]); - var end38 = int.Parse(cols[6]); - var onReverseStrand = cols[7] == "R"; - var hgncId = int.Parse(cols[8]); - var ensemblId = cols[9]; - var entrezGeneId = cols[10]; + string ucscRefName = cols[0]; + var chromosome = ReferenceNameUtilities.GetChromosome(_refNameToChromosome, ucscRefName); + string symbol = cols[2]; + int start37 = int.Parse(cols[3]); + int end37 = int.Parse(cols[4]); + int start38 = int.Parse(cols[5]); + int end38 = int.Parse(cols[6]); + bool onReverseStrand = cols[7] == "R"; + int hgncId = int.Parse(cols[8]); + string ensemblId = cols[9]; + string entrezGeneId = cols[10]; var grch37 = new Interval(start37, end37); var grch38 = new Interval(start38, end38); diff --git a/CacheUtils/Genes/UgaAssemblyCombiner.cs b/CacheUtils/Genes/UgaAssemblyCombiner.cs index fbea41cc..1c78eff7 100644 --- a/CacheUtils/Genes/UgaAssemblyCombiner.cs +++ b/CacheUtils/Genes/UgaAssemblyCombiner.cs @@ -21,7 +21,7 @@ public static UgaGene[] Combine(Dictionary> genesByRef37, var combiners = GetCombiners(); - foreach (var refIndex in referenceIndices.OrderBy(x => x)) + foreach (ushort refIndex in referenceIndices.OrderBy(x => x)) { var ugaGenesByRef = CombineByReference(GetUgaGenesByRef(genesByRef37, refIndex), GetUgaGenesByRef(genesByRef38, refIndex), combiners); @@ -38,8 +38,8 @@ private static List GetCombiners() => private static IEnumerable GetReferenceIndices(IEnumerable keysA, IEnumerable keysB) { var referenceIndices = new HashSet(); - foreach (var key in keysA) referenceIndices.Add(key); - foreach (var key in keysB) referenceIndices.Add(key); + foreach (ushort key in keysA) referenceIndices.Add(key); + foreach (ushort key in keysB) referenceIndices.Add(key); return referenceIndices.OrderBy(x => x); } diff --git a/CacheUtils/IntermediateIO/CcdsReader.cs b/CacheUtils/IntermediateIO/CcdsReader.cs index 77fc75b7..ec62359f 100644 --- a/CacheUtils/IntermediateIO/CcdsReader.cs +++ b/CacheUtils/IntermediateIO/CcdsReader.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using OptimizedCore; using VariantAnnotation.Utilities; namespace CacheUtils.IntermediateIO @@ -17,14 +18,14 @@ public static Dictionary> GetCcdsIdToEnsemblId(string ccdsP { while (true) { - var line = reader.ReadLine(); + string line = reader.ReadLine(); if (line == null) break; - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols.Length != 8) throw new InvalidDataException($"Expected 8 columns, but found {cols.Length}: [{line}]"); - var nucleotideId = cols[NucleotideIdIndex]; + string nucleotideId = cols[NucleotideIdIndex]; if (!nucleotideId.StartsWith("ENST")) continue; var ccds = FormatUtilities.SplitVersion(cols[CcdsIdIndex]); diff --git a/CacheUtils/IntermediateIO/GenbankReader.cs b/CacheUtils/IntermediateIO/GenbankReader.cs index 3e8573a7..f4dcd16f 100644 --- a/CacheUtils/IntermediateIO/GenbankReader.cs +++ b/CacheUtils/IntermediateIO/GenbankReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using CacheUtils.Genbank; +using OptimizedCore; using VariantAnnotation.Interface.Intervals; namespace CacheUtils.IntermediateIO @@ -49,7 +50,7 @@ private IInterval[] ReadExons(int numExons) string line = _reader.ReadLine(); if (line == null) throw new InvalidOperationException("Unexpected null line when parsing exons"); - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols[0] != "Exons") throw new InvalidDataException($"Expected the first keyword to be Exons, but found something different: {line}"); var exons = new IInterval[numExons]; @@ -68,7 +69,7 @@ private IInterval[] ReadExons(int numExons) private static (string TranscriptId, byte TranscriptVersion, string ProteinId, byte ProteinVersion, string GeneId, string GeneSymbol, IInterval CodingRegion, int NumExons) ReadTranscriptInfo(string line) { - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols.Length != 9) throw new InvalidDataException($"Expected 9 columns, but found {cols.Length} columns instead."); string transcriptId = cols[0]; diff --git a/CacheUtils/IntermediateIO/GenbankWriter.cs b/CacheUtils/IntermediateIO/GenbankWriter.cs index 0d63ef8f..aab13106 100644 --- a/CacheUtils/IntermediateIO/GenbankWriter.cs +++ b/CacheUtils/IntermediateIO/GenbankWriter.cs @@ -17,13 +17,13 @@ internal GenbankWriter(StreamWriter writer, IntermediateIoHeader header) internal void Write(GenbankEntry entry) { - var numExons = entry.Exons?.Length ?? 0; + int numExons = entry.Exons?.Length ?? 0; - var codingRegionStart = entry.CodingRegion?.Start ?? -1; - var codingRegionEnd = entry.CodingRegion?.End ?? -1; + int codingRegionStart = entry.CodingRegion?.Start ?? -1; + int codingRegionEnd = entry.CodingRegion?.End ?? -1; - var proteinId = entry.ProteinId ?? ""; - var proteinVersion = entry.ProteinVersion; + string proteinId = entry.ProteinId ?? ""; + byte proteinVersion = entry.ProteinVersion; _writer.WriteLine($"{entry.TranscriptId}\t{entry.TranscriptVersion}\t{proteinId}\t{proteinVersion}\t{entry.GeneId}\t{entry.Symbol}\t{codingRegionStart}\t{codingRegionEnd}\t{numExons}"); if (entry.Exons == null) return; diff --git a/CacheUtils/IntermediateIO/IntermediateIoHeader.cs b/CacheUtils/IntermediateIO/IntermediateIoHeader.cs index c02c5822..5b33fd07 100644 --- a/CacheUtils/IntermediateIO/IntermediateIoHeader.cs +++ b/CacheUtils/IntermediateIO/IntermediateIoHeader.cs @@ -1,4 +1,5 @@ using System.IO; +using OptimizedCore; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.Sequence; @@ -30,20 +31,20 @@ internal void Write(StreamWriter writer, IntermediateIoCommon.FileType fileType) internal static (string Id, IntermediateIoCommon.FileType Type, IntermediateIoHeader Header) Read(StreamReader reader) { - var cols = reader.ReadLine()?.Split('\t'); - var cols2 = reader.ReadLine()?.Split('\t'); + var cols = reader.ReadLine()?.OptimizedSplit('\t'); + var cols2 = reader.ReadLine()?.OptimizedSplit('\t'); if (cols == null || cols2 == null) throw new InvalidDataException("Found unexpected null lines when parsing the intermediate I/O file header"); - var id = cols[0]; - var type = (IntermediateIoCommon.FileType)byte.Parse(cols[1]); + string id = cols[0]; + var type = (IntermediateIoCommon.FileType)byte.Parse(cols[1]); - var vepVersion = ushort.Parse(cols2[0]); - var vepReleaseTicks = long.Parse(cols2[1]); - var source = (Source)byte.Parse(cols2[2]); - var genomeAssembly = (GenomeAssembly)byte.Parse(cols2[3]); - var numRefSeqs = int.Parse(cols2[4]); + ushort vepVersion = ushort.Parse(cols2[0]); + long vepReleaseTicks = long.Parse(cols2[1]); + var source = (Source)byte.Parse(cols2[2]); + var genomeAssembly = (GenomeAssembly)byte.Parse(cols2[3]); + int numRefSeqs = int.Parse(cols2[4]); var header = new IntermediateIoHeader(vepVersion, vepReleaseTicks, source, genomeAssembly, numRefSeqs); return (id, type, header); diff --git a/CacheUtils/IntermediateIO/LrgReader.cs b/CacheUtils/IntermediateIO/LrgReader.cs index 29ff2ee1..62b47fa7 100644 --- a/CacheUtils/IntermediateIO/LrgReader.cs +++ b/CacheUtils/IntermediateIO/LrgReader.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using OptimizedCore; using VariantAnnotation.Utilities; namespace CacheUtils.IntermediateIO @@ -18,11 +19,11 @@ public static HashSet GetTranscriptIds(string lrgPath, Dictionary GetTranscriptIds(string lrgPath, Dictionary positions) } _writer.Write($"Sec\t{positions.Count}"); - foreach (var pos in positions) _writer.Write($"\t{pos}"); + foreach (int pos in positions) _writer.Write($"\t{pos}"); _writer.WriteLine(); } @@ -89,7 +89,7 @@ private void WriteTranslation(ICodingRegion codingRegion, string proteinId, byte private static void WriteGene(TextWriter writer, MutableGene gene) { - var strand = gene.OnReverseStrand ? 'R' : 'F'; + char strand = gene.OnReverseStrand ? 'R' : 'F'; writer.WriteLine($"Gene\t{gene.GeneId}\t{gene.Chromosome.UcscName}\t{gene.Chromosome.Index}\t{gene.Start}\t{gene.End}\t{strand}\t{gene.Symbol}\t{(int)gene.SymbolSource}\t{gene.HgncId}"); } diff --git a/CacheUtils/IntermediateIO/PredictionReader.cs b/CacheUtils/IntermediateIO/PredictionReader.cs index a350f6fd..40c2c357 100644 --- a/CacheUtils/IntermediateIO/PredictionReader.cs +++ b/CacheUtils/IntermediateIO/PredictionReader.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using CommonUtilities; +using OptimizedCore; using VariantAnnotation.Interface.Sequence; namespace CacheUtils.IntermediateIO @@ -41,7 +42,7 @@ public PredictionReader(Stream stream, IDictionary refIndex private (IChromosome Chromosome, int NumPredictions) GetChromosomeHeader() { string line = _reader.ReadLine(); - var cols = line?.Split('\t'); + var cols = line?.OptimizedSplit('\t'); if (cols == null) throw new InvalidDataException("Found an unexpected null line when parsing the chromosome header in the prediction reader."); if (cols.Length != 3) throw new InvalidDataException($"Expected 3 columns in the chromosome header, but found {cols.Length}"); @@ -57,7 +58,7 @@ public PredictionReader(Stream stream, IDictionary refIndex string line = _reader.ReadLine(); if (line == null) throw new InvalidDataException("Found an unexpected empty line while parsing the prediction file."); - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols.Length != 2) throw new InvalidDataException($"Expected 2 columns in the prediction entry, but found {cols.Length}"); var transcriptIndices = GetTranscriptIndices(cols[0]); @@ -68,7 +69,7 @@ public PredictionReader(Stream stream, IDictionary refIndex private static List GetTranscriptIndices(string s) { - var indexStrings = s.Split(','); + var indexStrings = s.OptimizedSplit(','); var indices = new int[indexStrings.Length]; for (var i = 0; i < indexStrings.Length; i++) indices[i] = int.Parse(indexStrings[i]); return indices.ToList(); diff --git a/CacheUtils/IntermediateIO/PredictionWriter.cs b/CacheUtils/IntermediateIO/PredictionWriter.cs index 4cba778a..4804c567 100644 --- a/CacheUtils/IntermediateIO/PredictionWriter.cs +++ b/CacheUtils/IntermediateIO/PredictionWriter.cs @@ -25,7 +25,7 @@ internal void Write(IChromosome chromosome, Dictionary> predic private void WritePrediction(IEnumerable transcriptIds, string predictionData) { - var transcriptIdString = string.Join(',', transcriptIds); + string transcriptIdString = string.Join(',', transcriptIds); _writer.WriteLine($"{transcriptIdString}\t{predictionData}"); } diff --git a/CacheUtils/IntermediateIO/RegulatoryRegionReader.cs b/CacheUtils/IntermediateIO/RegulatoryRegionReader.cs index d521d43f..c8415eaa 100644 --- a/CacheUtils/IntermediateIO/RegulatoryRegionReader.cs +++ b/CacheUtils/IntermediateIO/RegulatoryRegionReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using CommonUtilities; +using OptimizedCore; using VariantAnnotation.AnnotatedPositions.Transcript; using VariantAnnotation.Caches.DataStructures; using VariantAnnotation.Interface.AnnotatedPositions; @@ -38,15 +39,15 @@ public IRegulatoryRegion[] GetRegulatoryRegions() private IRegulatoryRegion GetNextRegulatoryRegion() { - var line = _reader.ReadLine(); + string line = _reader.ReadLine(); if (line == null) return null; - var cols = line.Split('\t'); - var referenceIndex = ushort.Parse(cols[1]); - var start = int.Parse(cols[2]); - var end = int.Parse(cols[3]); - var id = CompactId.Convert(cols[4]); - var type = (RegulatoryRegionType)byte.Parse(cols[6]); + var cols = line.OptimizedSplit('\t'); + ushort referenceIndex = ushort.Parse(cols[1]); + int start = int.Parse(cols[2]); + int end = int.Parse(cols[3]); + var id = CompactId.Convert(cols[4]); + var type = (RegulatoryRegionType)byte.Parse(cols[6]); var chromosome = ReferenceNameUtilities.GetChromosome(_refIndexToChromosome, referenceIndex); return new RegulatoryRegion(chromosome, start, end, id, type); diff --git a/CacheUtils/MiniCache/DataBundle.cs b/CacheUtils/MiniCache/DataBundle.cs index 9d5fefd4..db53bbac 100644 --- a/CacheUtils/MiniCache/DataBundle.cs +++ b/CacheUtils/MiniCache/DataBundle.cs @@ -62,7 +62,7 @@ public static DataBundle GetDataBundle(string referencePath, string cachePrefix) { cacheData = transcriptReader.Read(sequenceReader.RefIndexToChromosome); cache = cacheData.GetCache(); - source = transcriptReader.Header.TranscriptSource; + source = transcriptReader.Header.Source; } return new DataBundle(sequenceReader, siftReader, polyPhenReader, cacheData, cache, source); diff --git a/CacheUtils/PredictionCache/PredictionCacheBuilder.cs b/CacheUtils/PredictionCache/PredictionCacheBuilder.cs index 733d1941..e8ef64c3 100644 --- a/CacheUtils/PredictionCache/PredictionCacheBuilder.cs +++ b/CacheUtils/PredictionCache/PredictionCacheBuilder.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using CacheUtils.DataDumperImport.DataStructures.Mutable; using CacheUtils.IntermediateIO; +using CacheUtils.Utilities; using VariantAnnotation.Caches.DataStructures; using VariantAnnotation.Interface; using VariantAnnotation.Interface.AnnotatedPositions; @@ -16,13 +16,11 @@ public sealed class PredictionCacheBuilder { private readonly ILogger _logger; private readonly GenomeAssembly _genomeAssembly; - private readonly long _currentTicks; public PredictionCacheBuilder(ILogger logger, GenomeAssembly genomeAssembly) { _logger = logger; _genomeAssembly = genomeAssembly; - _currentTicks = DateTime.Now.Ticks; } public (PredictionCacheStaging Sift, PredictionCacheStaging PolyPhen) CreatePredictionCaches( @@ -72,15 +70,14 @@ private PredictionCacheStaging BuildCacheStaging(string description, var predictionsPerRef = ConvertPredictions(roundedPredictionsPerRef, roundedEntryToLutIndex, lut); _logger.WriteLine("finished."); - var header = CreateHeader(numReferenceSeqs); - return new PredictionCacheStaging(header, lut, predictionsPerRef); + var header = CreateHeader(numReferenceSeqs, lut); + return new PredictionCacheStaging(header, predictionsPerRef); } - private CacheHeader CreateHeader(int numReferenceSeqs) + private PredictionHeader CreateHeader(int numReferenceSeqs, Prediction.Entry[] lut) { var customHeader = new PredictionCacheCustomHeader(new IndexEntry[numReferenceSeqs]); - return new CacheHeader(CacheConstants.Identifier, CacheConstants.SchemaVersion, CacheConstants.DataVersion, - Source.None, _currentTicks, _genomeAssembly, customHeader); + return new PredictionHeader(HeaderUtilities.GetHeader(Source.None, _genomeAssembly), customHeader, lut); } private static Prediction[][] ConvertPredictions(IReadOnlyList roundedPredictionsPerRef, diff --git a/CacheUtils/PredictionCache/PredictionCacheStaging.cs b/CacheUtils/PredictionCache/PredictionCacheStaging.cs index 8215c2d7..583fc93b 100644 --- a/CacheUtils/PredictionCache/PredictionCacheStaging.cs +++ b/CacheUtils/PredictionCache/PredictionCacheStaging.cs @@ -10,14 +10,12 @@ namespace CacheUtils.PredictionCache { public sealed class PredictionCacheStaging : IStaging { - private readonly Prediction.Entry[] _lookupTable; private readonly Prediction[][] _predictionsPerRef; - private readonly CacheHeader _header; + private readonly PredictionHeader _header; - internal PredictionCacheStaging(CacheHeader header, Prediction.Entry[] lut, Prediction[][] predictionsPerRef) + internal PredictionCacheStaging(PredictionHeader header, Prediction[][] predictionsPerRef) { _header = header; - _lookupTable = lut; _predictionsPerRef = predictionsPerRef; } @@ -26,7 +24,7 @@ public void Write(Stream stream) using (var blockStream = new BlockStream(new Zstandard(), stream, CompressionMode.Compress)) using (var writer = new PredictionCacheWriter(blockStream, _header)) { - writer.Write(_lookupTable, _predictionsPerRef); + writer.Write(_header.LookupTable, _predictionsPerRef); } } } diff --git a/CacheUtils/PredictionCache/PredictionCacheWriter.cs b/CacheUtils/PredictionCache/PredictionCacheWriter.cs index 989da117..3feb9431 100644 --- a/CacheUtils/PredictionCache/PredictionCacheWriter.cs +++ b/CacheUtils/PredictionCache/PredictionCacheWriter.cs @@ -11,10 +11,10 @@ public sealed class PredictionCacheWriter : IDisposable { private readonly BinaryWriter _writer; private readonly BlockStream _blockStream; - private readonly CacheHeader _header; + private readonly PredictionHeader _header; private readonly bool _leaveOpen; - public PredictionCacheWriter(BlockStream blockStream, CacheHeader header, bool leaveOpen = false) + public PredictionCacheWriter(BlockStream blockStream, PredictionHeader header, bool leaveOpen = false) { _blockStream = blockStream; _writer = new BinaryWriter(blockStream); @@ -41,11 +41,7 @@ internal void Write(Prediction.Entry[] lut, Prediction[][] predictionsPerRef) private void WritePredictions(IReadOnlyList predictionsPerRef) { - // ReSharper disable once UsePatternMatching - var customHeader = _header.CustomHeader as PredictionCacheCustomHeader; - if (customHeader == null) throw new InvalidCastException(); - - var indexEntries = customHeader.Entries; + var indexEntries = _header.Custom.Entries; var blockPosition = new BlockStream.BlockPosition(); for (var i = 0; i < predictionsPerRef.Count; i++) diff --git a/CacheUtils/TranscriptCache/TranscriptCacheBuilder.cs b/CacheUtils/TranscriptCache/TranscriptCacheBuilder.cs index c2c17be9..77fc250a 100644 --- a/CacheUtils/TranscriptCache/TranscriptCacheBuilder.cs +++ b/CacheUtils/TranscriptCache/TranscriptCacheBuilder.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using CacheUtils.DataDumperImport.DataStructures.Mutable; using CacheUtils.Genes.DataStructures; using CacheUtils.Genes.Utilities; +using CacheUtils.Utilities; using VariantAnnotation.Interface; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.Intervals; @@ -41,8 +41,7 @@ public TranscriptCacheStaging CreateTranscriptCache(MutableTranscript[] mutableT var regulatoryRegionIntervalArrays = regulatoryRegions.ToIntervalArrays(numRefSeqs); var customHeader = new TranscriptCacheCustomHeader(_vepVersion, _vepReleaseTicks); - var header = new CacheHeader(CacheConstants.Identifier, CacheConstants.SchemaVersion, - CacheConstants.DataVersion, _source, DateTime.Now.Ticks, _genomeAssembly, customHeader); + var header = new CacheHeader(HeaderUtilities.GetHeader(_source, _genomeAssembly), customHeader); return TranscriptCacheStaging.GetStaging(header, transcriptIntervalArrays, regulatoryRegionIntervalArrays); } @@ -56,7 +55,7 @@ private void AssignUgaGenesToTranscripts(IEnumerable transcri if (ugaGenes == null) { - var strand = originalGene.OnReverseStrand ? "R" : "F"; + string strand = originalGene.OnReverseStrand ? "R" : "F"; throw new InvalidDataException($"Found a transcript ({transcript.Id}) that does not have an overlapping UGA gene: gene ID: {originalGene.GeneId} {originalGene.Chromosome.UcscName} {originalGene.Start} {originalGene.End} {strand}"); } diff --git a/CacheUtils/Utilities/AccessionUtilities.cs b/CacheUtils/Utilities/AccessionUtilities.cs index db96786b..3dbd994f 100644 --- a/CacheUtils/Utilities/AccessionUtilities.cs +++ b/CacheUtils/Utilities/AccessionUtilities.cs @@ -8,7 +8,7 @@ internal static class AccessionUtilities { internal static (string Id, byte Version) GetMaxVersion(string originalId, byte originalVersion) { - var (pureId, idVersion) = FormatUtilities.SplitVersion(originalId); + (string pureId, byte idVersion) = FormatUtilities.SplitVersion(originalId); return (pureId, Math.Max(originalVersion, idVersion)); } diff --git a/CacheUtils/Utilities/HeaderUtilities.cs b/CacheUtils/Utilities/HeaderUtilities.cs new file mode 100644 index 00000000..9c9b0117 --- /dev/null +++ b/CacheUtils/Utilities/HeaderUtilities.cs @@ -0,0 +1,14 @@ +using System; +using VariantAnnotation.Interface.AnnotatedPositions; +using VariantAnnotation.Interface.Sequence; +using VariantAnnotation.IO.Caches; + +namespace CacheUtils.Utilities +{ + public static class HeaderUtilities + { + public static Header GetHeader(Source source, GenomeAssembly genomeAssembly) => new Header( + CacheConstants.Identifier, CacheConstants.SchemaVersion, CacheConstants.DataVersion, source, + DateTime.Now.Ticks, genomeAssembly); + } +} diff --git a/CommandLine/Builders/ValidationExtensions.cs b/CommandLine/Builders/ValidationExtensions.cs index b4c67351..854e2ce2 100644 --- a/CommandLine/Builders/ValidationExtensions.cs +++ b/CommandLine/Builders/ValidationExtensions.cs @@ -146,7 +146,7 @@ public static IConsoleAppValidator HasRequiredDate(this IConsoleAppValidator val validator.HasRequiredParameter(date, description, commandLineOption); if (string.IsNullOrEmpty(date)) return validator; - if (!DateTime.TryParse(date, out var _)) + if (!DateTime.TryParse(date, out _)) { validator.Data.AddError($"The {description} was not specified as a date (YYYY-MM-dd). Please use the {commandLineOption} parameter.", ExitCodes.BadArguments); } diff --git a/Compression/Compression.csproj b/Compression/Compression.csproj index bf8d83c3..91dd4e77 100644 --- a/Compression/Compression.csproj +++ b/Compression/Compression.csproj @@ -5,9 +5,7 @@ Full - - diff --git a/Compression/FileHandling/BgzipTextReader.cs b/Compression/FileHandling/BgzipTextReader.cs index b73683a2..d6ae51dd 100644 --- a/Compression/FileHandling/BgzipTextReader.cs +++ b/Compression/FileHandling/BgzipTextReader.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Text; -using CommonUtilities; namespace Compression.FileHandling { @@ -14,14 +13,15 @@ public sealed class BgzipTextReader : IDisposable private int _bufferIndex; private long _streamPosition; private const int BufferSize = BlockGZipStream.BlockGZipFormatCommon.BlockSize; + private readonly StringBuilder _sb = new StringBuilder(); public long Position => _streamPosition + _bufferIndex; - + public BgzipTextReader(Stream stream, char newLineChar = '\n') { - _buffer = new byte[BufferSize]; - _stream = stream; + _buffer = new byte[BufferSize]; + _stream = stream; _newLineChar = newLineChar; FillBuffer(); @@ -30,13 +30,13 @@ public BgzipTextReader(Stream stream, char newLineChar = '\n') private void FillBuffer() { _streamPosition = _stream.Position; - _bufferLength = _stream.Read(_buffer, 0, _buffer.Length); - _bufferIndex = 0; + _bufferLength = _stream.Read(_buffer, 0, _buffer.Length); + _bufferIndex = 0; } public string ReadLine() { - var sb = StringBuilderCache.Acquire(); + _sb.Clear(); //fill the buffer if empty if (_bufferIndex >= _bufferLength) @@ -48,20 +48,20 @@ public string ReadLine() while (_buffer[_bufferIndex++] != _newLineChar) { if (_bufferIndex < _bufferLength) continue; - // the lenght should be (_bufferIndex - startIndex +1 ) but since _buffer index is already incremented, it is (_bufferIndex - startIndex) - sb.Append(Encoding.UTF8.GetString(SubArray(_buffer, startIndex, _bufferIndex - startIndex))); + + // the length should be (_bufferIndex - startIndex +1 ) but since _buffer index is already incremented, it is (_bufferIndex - startIndex) + _sb.Append(Encoding.UTF8.GetString(SubArray(_buffer, startIndex, _bufferIndex - startIndex))); FillBuffer(); startIndex = _bufferIndex; if (_bufferLength == 0) break; } - // we do not want the last char (new line), therefore, the length of subarray is _bufferIndex - startIndex -1 - sb.Append(_buffer[_bufferIndex - 1] == _newLineChar + // we do not want the last char (new line), therefore, the length of subarray is _bufferIndex - startIndex -1 + _sb.Append(_buffer[_bufferIndex - 1] == _newLineChar ? Encoding.UTF8.GetString(SubArray(_buffer, startIndex, _bufferIndex - startIndex - 1)) : Encoding.UTF8.GetString(SubArray(_buffer, startIndex, _bufferIndex - startIndex))); - string result = StringBuilderCache.GetStringAndRelease(sb); - return result.Length == 0 ? null : result; + return _sb.Length == 0 ? null : _sb.ToString(); } private static T[] SubArray(T[] data, int index, int length) @@ -71,9 +71,6 @@ private static T[] SubArray(T[] data, int index, int length) return result; } - public void Dispose() - { - _stream.Dispose(); - } + public void Dispose() => _stream.Dispose(); } } \ No newline at end of file diff --git a/Compression/FileHandling/BlockStream.cs b/Compression/FileHandling/BlockStream.cs index 67e359b2..84fd6b73 100644 --- a/Compression/FileHandling/BlockStream.cs +++ b/Compression/FileHandling/BlockStream.cs @@ -5,7 +5,6 @@ using Compression.Algorithms; using Compression.DataStructures; using ErrorHandling.Exceptions; -using VariantAnnotation.Interface.IO; namespace Compression.FileHandling { @@ -112,14 +111,6 @@ public void WriteHeader(Action headerWrite) _headerWrite(_writer); } - public IFileHeader ReadHeader(Func, IFileHeader> headerRead, - Func customRead) - { - IFileHeader header; - using (var reader = new BinaryReader(_stream, Encoding.UTF8, true)) header = headerRead(reader, customRead); - return header; - } - public override int Read(byte[] buffer, int offset, int count) { if (_foundEof) return 0; diff --git a/Jasix/IndexCreator.cs b/Jasix/IndexCreator.cs index 2694155d..865795ed 100644 --- a/Jasix/IndexCreator.cs +++ b/Jasix/IndexCreator.cs @@ -8,6 +8,7 @@ using Jasix.DataStructures; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OptimizedCore; using VariantAnnotation.Utilities; @@ -101,7 +102,7 @@ public void CreateIndex() int previousPos = 0; while ((line = _reader.ReadLine()) != null) { - if (line.StartsWith("]")) break; + if (line.OptimizedStartsWith(']')) break; line = line.TrimEnd(','); var chrPos = GetChromPosition(line); diff --git a/Jasix/Jasix.csproj b/Jasix/Jasix.csproj index d4372770..37a6f079 100644 --- a/Jasix/Jasix.csproj +++ b/Jasix/Jasix.csproj @@ -10,6 +10,7 @@ + diff --git a/Jasix/QueryProcessor.cs b/Jasix/QueryProcessor.cs index 384a62fb..e52d09f0 100644 --- a/Jasix/QueryProcessor.cs +++ b/Jasix/QueryProcessor.cs @@ -3,6 +3,7 @@ using System.IO; using Jasix.DataStructures; using Newtonsoft.Json; +using OptimizedCore; using VariantAnnotation.Algorithms; namespace Jasix @@ -165,7 +166,7 @@ internal IEnumerable ReadOverlappingJsonLines((string Chr, int Start, in RepositionReader(position); string line; - while ((line = _jsonReader.ReadLine()) != null && !line.StartsWith("]")) + while ((line = _jsonReader.ReadLine()) != null && !line.OptimizedStartsWith(']')) //The array of positions entry end with "]," Going past it will cause the json parser to crash { line = line.TrimEnd(','); diff --git a/Nirvana.sln b/Nirvana.sln index c38bc0af..911483fd 100644 --- a/Nirvana.sln +++ b/Nirvana.sln @@ -27,7 +27,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SAUtils", "SAUtils\SAUtils. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jasix", "Jasix\Jasix.csproj", "{ECC7869C-1B21-42C1-B8BD-4190F15B3B6F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom", "Phantom\Phantom.csproj", "{86DB686A-17A8-446B-A2C4-100CD6ADCB39}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Phantom", "Phantom\Phantom.csproj", "{86DB686A-17A8-446B-A2C4-100CD6ADCB39}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptimizedCore", "OptimizedCore\OptimizedCore.csproj", "{76FEE3B3-FB8E-4421-A63F-CA659FB1ACA0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -87,6 +89,10 @@ Global {86DB686A-17A8-446B-A2C4-100CD6ADCB39}.Debug|Any CPU.Build.0 = Debug|Any CPU {86DB686A-17A8-446B-A2C4-100CD6ADCB39}.Release|Any CPU.ActiveCfg = Release|Any CPU {86DB686A-17A8-446B-A2C4-100CD6ADCB39}.Release|Any CPU.Build.0 = Release|Any CPU + {76FEE3B3-FB8E-4421-A63F-CA659FB1ACA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76FEE3B3-FB8E-4421-A63F-CA659FB1ACA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76FEE3B3-FB8E-4421-A63F-CA659FB1ACA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76FEE3B3-FB8E-4421-A63F-CA659FB1ACA0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Nirvana/Nirvana.cs b/Nirvana/Nirvana.cs index 0b1f8f1f..11de91d9 100644 --- a/Nirvana/Nirvana.cs +++ b/Nirvana/Nirvana.cs @@ -55,17 +55,18 @@ private ExitCodes ProgramExecution() var conservationProvider = ProviderUtilities.GetConservationProvider(SupplementaryAnnotationDirectories); var refMinorProvider = ProviderUtilities.GetRefMinorProvider(SupplementaryAnnotationDirectories); var geneAnnotationProvider = ProviderUtilities.GetGeneAnnotationProvider(SupplementaryAnnotationDirectories); - var plugins = PluginUtilities.LoadPlugins(_pluginDirectory); - var annotator = ProviderUtilities.GetAnnotator(transcriptAnnotationProvider, sequenceProvider, saProvider, conservationProvider, geneAnnotationProvider, plugins); + + var plugins = PluginUtilities.LoadPlugins(_pluginDirectory); + var annotator = ProviderUtilities.GetAnnotator(transcriptAnnotationProvider, sequenceProvider, saProvider, conservationProvider, geneAnnotationProvider, plugins); var recomposer = _disableRecomposition ? new NullRecomposer() : Recomposer.Create(sequenceProvider, _inputCachePrefix); - var logger = _outputFileName == "-" ? (ILogger) new NullLogger() : new ConsoleLogger(); - var metrics = new PerformanceMetrics(logger); + var logger = _outputFileName == "-" ? (ILogger)new NullLogger() : new ConsoleLogger(); + var metrics = new PerformanceMetrics(logger); var dataSourceVersions = GetDataSourceVersions(plugins, transcriptAnnotationProvider, saProvider, geneAnnotationProvider, conservationProvider); - var vepDataVersion = transcriptAnnotationProvider.VepVersion + "." + CacheConstants.DataVersion + "." + SaDataBaseCommon.DataVersion; - var jasixFileName = _outputFileName + ".json.gz" + JasixCommons.FileExt; + string vepDataVersion = transcriptAnnotationProvider.VepVersion + "." + CacheConstants.DataVersion + "." + SaDataBaseCommon.DataVersion; + string jasixFileName = _outputFileName + ".json.gz" + JasixCommons.FileExt; using (var outputWriter = ReadWriteUtilities.GetOutputWriter(_outputFileName)) using (var vcfReader = ReadWriteUtilities.GetVcfReader(_vcfPath, sequenceProvider.RefNameToChromosome, refMinorProvider, _reportAllSvOverlappingTranscripts, recomposer)) diff --git a/Nirvana/PluginUtilities.cs b/Nirvana/PluginUtilities.cs index c73fafc4..240af93b 100644 --- a/Nirvana/PluginUtilities.cs +++ b/Nirvana/PluginUtilities.cs @@ -9,19 +9,19 @@ namespace Nirvana { public static class PluginUtilities { - private const string DllExtension = ".dll"; + private const string DllExtension = ".dll"; private const string ConfigExtension = ".config"; + public static IPlugin[] LoadPlugins(string pluginDirectory) { - var executableLocation = Assembly.GetEntryAssembly().Location; - var path = pluginDirectory ?? Path.Combine(Path.GetDirectoryName(executableLocation), "Plugins"); + string executableLocation = Assembly.GetEntryAssembly().Location; + string path = pluginDirectory ?? Path.Combine(Path.GetDirectoryName(executableLocation), "Plugins"); if (!Directory.Exists(path)) return null; var pluginFileNames = Directory.GetFiles(path, "*.dll", SearchOption.TopDirectoryOnly); - var assemblies = pluginFileNames.Select(AssemblyLoadContext.Default.LoadFromAssemblyPath).ToArray(); - - var configuration = new ContainerConfiguration().WithAssemblies(assemblies); + var assemblies = pluginFileNames.Select(AssemblyLoadContext.Default.LoadFromAssemblyPath).ToArray(); + var configuration = new ContainerConfiguration().WithAssemblies(assemblies); IPlugin[] plugins; using (var container = configuration.CreateContainer()) @@ -31,12 +31,11 @@ public static IPlugin[] LoadPlugins(string pluginDirectory) foreach (var plugin in plugins) { - var configFilePath = Path.Combine(path, plugin.Name + DllExtension + ConfigExtension); - if (!File.Exists(configFilePath)) - throw new FileNotFoundException($"Missing expected config file: {configFilePath}!!"); + string configFilePath = Path.Combine(path, plugin.Name + DllExtension + ConfigExtension); + if (!File.Exists(configFilePath)) throw new FileNotFoundException($"Missing expected config file: {configFilePath}!!"); } + return plugins; } - } } \ No newline at end of file diff --git a/OptimizedCore/OptimizedCore.csproj b/OptimizedCore/OptimizedCore.csproj new file mode 100644 index 00000000..8dd8c39b --- /dev/null +++ b/OptimizedCore/OptimizedCore.csproj @@ -0,0 +1,14 @@ + + + netcoreapp2.0 + ..\bin\$(Configuration) + Full + + + true + + + true + + + diff --git a/OptimizedCore/StringExtensions.cs b/OptimizedCore/StringExtensions.cs new file mode 100644 index 00000000..3bf343b0 --- /dev/null +++ b/OptimizedCore/StringExtensions.cs @@ -0,0 +1,99 @@ +using System; + +namespace OptimizedCore +{ + public static class StringExtensions + { + public static unsafe string[] OptimizedSplit(this string s, char delimiter) + { + var numReplaces = 0; + int sLen = s.Length; + var sepList = new int[s.Length]; + + // find the locations of our tab delimiter + fixed (char* chPtr = s) + { + for (var index = 0; index < sLen; ++index) + { + if (chPtr[index] == delimiter) sepList[numReplaces++] = index; + } + } + + // extract our columns + var startIndex = 0; + var colIndex = 0; + + var columns = new string[numReplaces + 1]; + for (var index = 0; index < numReplaces && startIndex < sLen; ++index) + { + columns[colIndex++] = s.Substring(startIndex, sepList[index] - startIndex); + startIndex = sepList[index] + 1; + } + + // handle the last column + if (startIndex < sLen && numReplaces >= 0) columns[colIndex] = s.Substring(startIndex); + else if (colIndex == numReplaces) columns[colIndex] = string.Empty; + + return columns; + } + + public static (string Key, string Value) OptimizedKeyValue(this string s) + { + int equalPos = s.IndexOf('='); + return equalPos == -1 ? (s, null) : (s.Substring(0, equalPos), s.Substring(equalPos + 1)); + } + + ///

+ /// handles -2_147_483_647 to +2_147_483_647 + /// + public static unsafe (int Number, bool FoundError) OptimizedParseInt32(this string s) + { + var number = 0; + + // 2_147_483_647 + if (string.IsNullOrEmpty(s) || s.Length > 11) return (0, true); + + try + { + fixed (char* chPtr = s) + { + int index = s.Length - 1; + var ptr = chPtr; + var applyNegative = false; + + if (*ptr == '-') + { + applyNegative = true; + ptr++; + index--; + } + + while (index >= 0) + { + if (*ptr < 48 || *ptr > 57) return (0, true); + + checked + { + number *= 10; + number += *ptr++ - '0'; + } + + index--; + } + + if (applyNegative) number = -number; + } + } + catch (OverflowException) + { + return (0, true); + } + + return (number, false); + } + + public static bool OptimizedStartsWith(this string s, char ch) => s.Length > 0 && s[0] == ch; + + public static bool OptimizedEndsWith(this string s, char ch) => s.Length > 0 && s[s.Length - 1] == ch; + } +} diff --git a/Phantom/DataStructures/Genotype.cs b/Phantom/DataStructures/Genotype.cs index 5d648d05..4341c542 100644 --- a/Phantom/DataStructures/Genotype.cs +++ b/Phantom/DataStructures/Genotype.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using OptimizedCore; namespace Phantom.DataStructures { @@ -19,10 +20,14 @@ public Genotype(int[] alleleIndexes, bool isPhased) public static Genotype GetGenotype(string genotypeString) { char separator = GetGenotypeSeparator(genotypeString); - var gtIndexStrings = genotypeString.Split(separator); + var gtIndexStrings = genotypeString.OptimizedSplit(separator); var gtIndexes = new int[gtIndexStrings.Length]; for (var i = 0; i < gtIndexStrings.Length; i++) - gtIndexes[i] = gtIndexStrings[i] == "." ? -1 : int.Parse(gtIndexStrings[i]); + { + (int number, bool foundError) = gtIndexStrings[i].OptimizedParseInt32(); + gtIndexes[i] = foundError ? -1 : number; + } + return new Genotype(gtIndexes, separator == '|'); } diff --git a/Phantom/DataStructures/PositionSet.cs b/Phantom/DataStructures/PositionSet.cs index 64b0c0d0..0b4db7ac 100644 --- a/Phantom/DataStructures/PositionSet.cs +++ b/Phantom/DataStructures/PositionSet.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using OptimizedCore; using Phantom.Interfaces; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Positions; @@ -56,7 +57,7 @@ public static PositionSet CreatePositionSet(List simpleSimplePo for (int sampleIndex = 0; sampleIndex < positionSet.NumSamples; sampleIndex++) { int sampleColIndex = sampleIndex + VcfCommon.GenotypeIndex; - sampleInfo[i, sampleIndex] = positionSet.SimplePositions[i].VcfFields[sampleColIndex].Split(":"); + sampleInfo[i, sampleIndex] = positionSet.SimplePositions[i].VcfFields[sampleColIndex].OptimizedSplit(':'); } } return sampleInfo; @@ -89,7 +90,7 @@ internal int[][] GetSampleTagIndexes(string[] tagsToExtract) for (int i = 0; i < _numPositions; i++) { - var tags = SimplePositions[i].VcfFields[VcfCommon.FormatIndex].Split(":"); + var tags = SimplePositions[i].VcfFields[VcfCommon.FormatIndex].OptimizedSplit(':'); int numTagsExtracted = 0; for (int j = 0; j < tags.Length; j++) { @@ -186,7 +187,7 @@ private static HashSet[] GetAllelesWithUnsupportedTypes(PositionSet positio private static bool IsSupportedVariantType(string refAllele, string altAllele) { - return !(altAllele.StartsWith("<") || altAllele == "*") && SupportedVariantTypes.Contains(Vcf.VariantCreator.SmallVariantCreator.GetVariantType(refAllele, altAllele)); + return !(altAllele.OptimizedStartsWith('<') || altAllele == "*") && SupportedVariantTypes.Contains(Vcf.VariantCreator.SmallVariantCreator.GetVariantType(refAllele, altAllele)); } } diff --git a/Phantom/Interfaces/ICodonInfoProvider.cs b/Phantom/Interfaces/ICodonInfoProvider.cs index a1e1dc45..cd3e78d8 100644 --- a/Phantom/Interfaces/ICodonInfoProvider.cs +++ b/Phantom/Interfaces/ICodonInfoProvider.cs @@ -1,5 +1,4 @@ using VariantAnnotation.Interface.Intervals; -using VariantAnnotation.Interface.Positions; namespace Phantom.Interfaces { diff --git a/Phantom/Interfaces/IPositionBuffer.cs b/Phantom/Interfaces/IPositionBuffer.cs index ca813b26..ba2f61b9 100644 --- a/Phantom/Interfaces/IPositionBuffer.cs +++ b/Phantom/Interfaces/IPositionBuffer.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using Phantom.DataStructures; +using Phantom.DataStructures; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.Intervals; using VariantAnnotation.Interface.Positions; diff --git a/Phantom/Interfaces/IVariantGenerator.cs b/Phantom/Interfaces/IVariantGenerator.cs index b3e2dd9e..f7b8c5c7 100644 --- a/Phantom/Interfaces/IVariantGenerator.cs +++ b/Phantom/Interfaces/IVariantGenerator.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Phantom.DataStructures; using VariantAnnotation.Interface.Positions; namespace Phantom.Interfaces diff --git a/Phantom/Phantom.csproj b/Phantom/Phantom.csproj index 18718521..8f6b6fe1 100644 --- a/Phantom/Phantom.csproj +++ b/Phantom/Phantom.csproj @@ -17,6 +17,7 @@ + diff --git a/Phantom/Workers/PositionProcessor.cs b/Phantom/Workers/PositionProcessor.cs index c1312cbb..802d7374 100644 --- a/Phantom/Workers/PositionProcessor.cs +++ b/Phantom/Workers/PositionProcessor.cs @@ -3,8 +3,6 @@ using Phantom.DataStructures; using Phantom.Interfaces; using VariantAnnotation.Interface.Positions; -using VariantAnnotation.Interface.IO; -using Vcf; namespace Phantom.Workers { diff --git a/SAUtils/CreateOmimTsv/OmimPhenotype.cs b/SAUtils/CreateOmimTsv/OmimPhenotype.cs index 865c2f77..1d316b2e 100644 --- a/SAUtils/CreateOmimTsv/OmimPhenotype.cs +++ b/SAUtils/CreateOmimTsv/OmimPhenotype.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using OptimizedCore; using VariantAnnotation.GeneAnnotation; namespace SAUtils.CreateOmimTsv @@ -14,7 +15,7 @@ public static class OmimPhenotype if (string.IsNullOrEmpty(line)) return phenotypes; - var infos = line.Split(';'); + var infos = line.OptimizedSplit(';'); phenotypes.AddRange(infos.Select(ExtractPhenotype)); return phenotypes; @@ -44,7 +45,7 @@ private static HashSet ExtractInheritances(string inheritance) var inheritances = new HashSet(); if (string.IsNullOrEmpty(inheritance)) return inheritances; - foreach (var content in inheritance.Split(',')) + foreach (var content in inheritance.OptimizedSplit(',')) { var trimmedContent = content.Trim(' '); inheritances.Add(trimmedContent); @@ -65,11 +66,11 @@ private static void ParsePhenotypeMapping(string phenotypeGroup, out string phen } else { - if (phenotypeGroup.StartsWith("{")) + if (phenotypeGroup.OptimizedStartsWith('{')) { comments = OmimEntry.Comments.contribute_to_susceptibility_to_multifactorial_disorders_or_to_susceptibility_to_infection; } - else if (phenotypeGroup.StartsWith("[")) + else if (phenotypeGroup.OptimizedStartsWith('[')) { comments = OmimEntry.Comments.nondiseases; } diff --git a/SAUtils/CreateOmimTsv/OmimReader.cs b/SAUtils/CreateOmimTsv/OmimReader.cs index fee3bdbd..9feb15ae 100644 --- a/SAUtils/CreateOmimTsv/OmimReader.cs +++ b/SAUtils/CreateOmimTsv/OmimReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using OptimizedCore; namespace SAUtils.CreateOmimTsv { @@ -33,7 +34,7 @@ public void AddOmimEntries(Dictionary omimEntries) if (IsCommentLine(line)) continue; - var contents = line.Split('\t'); + var contents = line.OptimizedSplit('\t'); var mimNumber = Convert.ToInt32(contents[_mimNumberCol]); var geneSymbol = contents[_hgncCol]; var description = _geneDescriptionCol >= 0 ? contents[_geneDescriptionCol].Replace(@"\\'", @"'") : null; @@ -52,7 +53,7 @@ public void AddOmimEntries(Dictionary omimEntries) private void ParseHeader(string line) { line = line.Trim('#').Trim(' '); - var colNames = line.Split('\t').Select(x => x.Trim(' ')).ToList(); + var colNames = line.OptimizedSplit('\t').Select(x => x.Trim(' ')).ToList(); for (var index = 0; index < colNames.Count; index++) { @@ -87,7 +88,7 @@ private void ParseHeader(string line) private static bool IsHeader(string line) => line.StartsWith("# Chromosome\t") || line.StartsWith("# MIM Number\t"); - private static bool IsCommentLine(string line) => line.StartsWith("#"); + private static bool IsCommentLine(string line) => line.OptimizedStartsWith('#'); public void Dispose() => _stream.Dispose(); } diff --git a/SAUtils/DbSnpRemapper/ChromMapper.cs b/SAUtils/DbSnpRemapper/ChromMapper.cs index b15f0a01..898ed33f 100644 --- a/SAUtils/DbSnpRemapper/ChromMapper.cs +++ b/SAUtils/DbSnpRemapper/ChromMapper.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using Compression.Utilities; +using OptimizedCore; using VariantAnnotation.Interface.IO; namespace SAUtils.DbSnpRemapper @@ -33,11 +34,11 @@ public Dictionary Map() //read to the first data line while ((srcLine = _srcReader.ReadLine()) != null) { - if (!srcLine.StartsWith("#")) break; + if (!srcLine.OptimizedStartsWith('#')) break; } while ((destLine= _destReader.ReadLine()) != null) { - if (!destLine.StartsWith("#")) break; + if (!destLine.OptimizedStartsWith('#')) break; } // dictionary of leftover rsIds from previous chromosomes @@ -48,18 +49,10 @@ public Dictionary Map() destRsidLocations.Clear(); destLine = GetNextChromDestinations(destLine, destRsidLocations); srcLine = ProcessNextChromSource(srcLine, destRsidLocations); - - //debug - //if (srcLine.StartsWith("chr3")) break; - } - + } } // these writers need to be kept open so that the leftover mapper can append to them - //foreach (var writer in _writers.Values) - //{ - // writer.Dispose(); - //} Console.WriteLine($"Total leftover count:{_leftoverCount}"); return _writers; } diff --git a/SAUtils/DbSnpRemapper/LeftoverMapper.cs b/SAUtils/DbSnpRemapper/LeftoverMapper.cs index 69de7603..e6690894 100644 --- a/SAUtils/DbSnpRemapper/LeftoverMapper.cs +++ b/SAUtils/DbSnpRemapper/LeftoverMapper.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using Compression.Utilities; +using OptimizedCore; using VariantAnnotation.Interface.IO; namespace SAUtils.DbSnpRemapper @@ -44,7 +45,7 @@ public int Map() var leftoversWithDest = new Dictionary(); while ((line = _destReader.ReadLine()) != null) { - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; var splits = line.Split('\t', 4); var ids = Utilities.GetRsids(splits[VcfCommon.IdIndex]); diff --git a/SAUtils/DbSnpRemapper/Utilities.cs b/SAUtils/DbSnpRemapper/Utilities.cs index 32c94ac4..4db4b4a8 100644 --- a/SAUtils/DbSnpRemapper/Utilities.cs +++ b/SAUtils/DbSnpRemapper/Utilities.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using OptimizedCore; namespace SAUtils.DbSnpRemapper { @@ -7,7 +8,7 @@ public static class Utilities { public static long[] GetRsids(string idField) { - var ids = (from idStr in idField.Split(';') where idStr.StartsWith("rs") select Int64.Parse(idStr.Substring(2))).ToArray(); + var ids = (from idStr in idField.OptimizedSplit(';') where idStr.StartsWith("rs") select Int64.Parse(idStr.Substring(2))).ToArray(); return ids.Length == 0 ? null : ids; } diff --git a/SAUtils/ExtractCosmicSvs/CosmicCnvReader.cs b/SAUtils/ExtractCosmicSvs/CosmicCnvReader.cs index 46e68f52..06466c0e 100644 --- a/SAUtils/ExtractCosmicSvs/CosmicCnvReader.cs +++ b/SAUtils/ExtractCosmicSvs/CosmicCnvReader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using OptimizedCore; using VariantAnnotation.Interface.Positions; using VariantAnnotation.Interface.Sequence; @@ -109,7 +110,7 @@ internal void GetColumnIndices(string headerLine) _chromStartStopIndex = -1; _studyIdIndex = -1; - var columns = headerLine.Split('\t'); + var columns = headerLine.OptimizedSplit('\t'); for (int i = 0; i < columns.Length; i++) { switch (columns[i]) @@ -175,7 +176,7 @@ internal void GetColumnIndices(string headerLine) private CosmicCnvItem ExtractCosmicCnv(string line) { - var splits = line.Split('\t'); + var splits = line.OptimizedSplit('\t'); if (splits.Length == 1) return null; diff --git a/SAUtils/GeneScoresTsv/GeneScoreTsvCreator.cs b/SAUtils/GeneScoresTsv/GeneScoreTsvCreator.cs index 3bbe7c36..5ccd7bcb 100644 --- a/SAUtils/GeneScoresTsv/GeneScoreTsvCreator.cs +++ b/SAUtils/GeneScoresTsv/GeneScoreTsvCreator.cs @@ -4,6 +4,7 @@ using System.Linq; using CommonUtilities; using ErrorHandling; +using OptimizedCore; using SAUtils.TsvWriters; using VariantAnnotation.IO; @@ -99,7 +100,7 @@ private void WriteScores(string gene, double pLi, double pRec, double pNull) private (string gene, double pLi, double pRec, double pNull) GetGeneAndScores(string line) { - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); var gene = cols[_geneIndex]; var pLi = double.Parse(cols[_pliIndex]); var pRec = double.Parse(cols[_precIndex]); @@ -110,7 +111,7 @@ private void WriteScores(string gene, double pLi, double pRec, double pNull) private void GetColumnIndices(string line) { - var cols = line.Split("\t"); + var cols = line.OptimizedSplit('\t'); _geneIndex = Array.IndexOf(cols, GeneTag); _pliIndex = Array.IndexOf(cols, PliTag); diff --git a/SAUtils/InputFileParsers/ClinGen/ClinGenReader.cs b/SAUtils/InputFileParsers/ClinGen/ClinGenReader.cs index ea805180..13469b90 100644 --- a/SAUtils/InputFileParsers/ClinGen/ClinGenReader.cs +++ b/SAUtils/InputFileParsers/ClinGen/ClinGenReader.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using Compression.Utilities; +using OptimizedCore; using SAUtils.DataStructures; using VariantAnnotation.Interface.Positions; using VariantAnnotation.Interface.Sequence; @@ -33,7 +34,7 @@ public IEnumerable GetClinGenItems() { if (IsClinGenHeader(line)) continue; - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); string id = cols[0]; string ucscChrom = cols[1]; if(!_refNameDict.ContainsKey(ucscChrom)) continue; @@ -45,8 +46,8 @@ public IEnumerable GetClinGenItems() var variantType = GetVariantType(cols[6]); var clinInterpretation = GetClinInterpretation(cols[7]); bool validated = cols[8].Equals("True"); - var phenotypes = cols[9] == "" ? null : new HashSet(cols[9].Split(',')); - var phenotypeIds = cols[10] == "" ? null : new HashSet(cols[10].Split(',')); + var phenotypes = cols[9] == "" ? null : new HashSet(cols[9].OptimizedSplit(',')); + var phenotypeIds = cols[10] == "" ? null : new HashSet(cols[10].OptimizedSplit(',')); var currentItem = new ClinGenItem(id, chrom, start, end, variantType, observedGains, observedLosses, clinInterpretation, validated, phenotypes, phenotypeIds); @@ -91,7 +92,7 @@ private static ClinicalInterpretation GetClinInterpretation(string s) private static bool IsClinGenHeader(string line) { - return line.StartsWith("#"); + return line.OptimizedStartsWith('#'); } } } diff --git a/SAUtils/InputFileParsers/Cosmic/MergedCosmicReader.cs b/SAUtils/InputFileParsers/Cosmic/MergedCosmicReader.cs index aaf1d5c1..592bcb9b 100644 --- a/SAUtils/InputFileParsers/Cosmic/MergedCosmicReader.cs +++ b/SAUtils/InputFileParsers/Cosmic/MergedCosmicReader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using OptimizedCore; using SAUtils.DataStructures; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Sequence; @@ -79,7 +80,7 @@ public IEnumerable GetCosmicItems() // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; // Skip comments. - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; var cosmicItems = ExtractCosmicItems(line); if (cosmicItems == null) continue; @@ -93,7 +94,7 @@ public IEnumerable GetCosmicItems() private void AddCosmicStudy(string line) { - var columns = line.Split('\t'); + var columns = line.OptimizedSplit('\t'); var mutationId = columns[_mutationIdIndex]; var studyId = columns[_studyIdIndex]; @@ -152,7 +153,7 @@ private void GetColumnIndexes(string headerLine) _primarySiteIndex = -1; _primaryHistologyIndex = -1; - var columns = headerLine.Split('\t'); + var columns = headerLine.OptimizedSplit('\t'); for (int i = 0; i < columns.Length; i++) { switch (columns[i]) @@ -214,7 +215,7 @@ internal List ExtractCosmicItems(string vcfLine) var position = int.Parse(splitLine[VcfCommon.PosIndex]); var cosmicId = splitLine[VcfCommon.IdIndex]; var refAllele = splitLine[VcfCommon.RefIndex]; - var altAlleles = splitLine[VcfCommon.AltIndex].Split(','); + var altAlleles = splitLine[VcfCommon.AltIndex].OptimizedSplit(','); var infoField = splitLine[VcfCommon.InfoIndex]; Clear(); @@ -244,20 +245,16 @@ private void Clear() private void ParseInfoField(string infoFields) { if (infoFields == "" || infoFields == ".") return; + var infoItems = infoFields.OptimizedSplit(';'); - var infoItems = infoFields.Split(';'); - foreach (var infoItem in infoItems) + foreach (string infoItem in infoItems) { if (string.IsNullOrEmpty(infoItem)) continue; - var infoKeyValue = infoItem.Split('='); - if (infoKeyValue.Length == 2)//sanity check - { - var key = infoKeyValue[0]; - var value = infoKeyValue[1]; + (string key, string value) = infoItem.OptimizedKeyValue(); - SetInfoField(key, value); - } + // sanity check + if (value != null) SetInfoField(key, value); } } diff --git a/SAUtils/InputFileParsers/CustomAnnotation/CustomAnnotationReader.cs b/SAUtils/InputFileParsers/CustomAnnotation/CustomAnnotationReader.cs index b531c36e..d5be6dba 100644 --- a/SAUtils/InputFileParsers/CustomAnnotation/CustomAnnotationReader.cs +++ b/SAUtils/InputFileParsers/CustomAnnotation/CustomAnnotationReader.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Compression.Utilities; +using OptimizedCore; using SAUtils.DataStructures; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Sequence; @@ -55,7 +56,7 @@ private void ReadHeader() { // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; - if (line.StartsWith("#")) + if (line.OptimizedStartsWith('#')) { ParseHeaderLine(line); } @@ -82,7 +83,7 @@ public IEnumerable GetCustomItems() { // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; - if (line.StartsWith("#")) + if (line.OptimizedStartsWith('#')) { continue; } @@ -97,93 +98,82 @@ public IEnumerable GetCustomItems() } } - private void GetTopLevelKey(string line) - { - //##IAE_TOP= - line = line.Substring(11);//removing ##IAE_TOP=< - line = line.Substring(0, line.Length - 1);//removing the last '>' - var fields= line.Split(','); - - foreach (var field in fields) - { - var keyValue = field.Split('='); - var key = keyValue[0]; - var value = keyValue[1]; - switch (key) - { - case "KEY": - _topKey = value; - break; - case "MATCH": - // default is allele specific - IsPositional = value == "Position"; - break; - default: - throw new Exception("Unknown field in top level key line :\n "+ line); - - } - } - - } + private void GetTopLevelKey(string line) + { + //##IAE_TOP= + line = line.Substring(11);//removing ##IAE_TOP=< + line = line.Substring(0, line.Length - 1);//removing the last '>' + var fields = line.OptimizedSplit(','); - private void AddInfoField(string line) - { - //##IAE_INFO= - line = line.Substring(12);//removing ##IAE_INFO=< - line = line.Substring(0, line.Length - 1);//removing the last '>' - - var fields = line.Split(','); + foreach (string field in fields) + { + (string key, string value) = field.OptimizedKeyValue(); - string info=null, type=null, json=null; - - foreach (var field in fields) - { - var keyValue = field.Split('='); + switch (key) + { + case "KEY": + _topKey = value; + break; + case "MATCH": + // default is allele specific + IsPositional = value == "Position"; + break; + default: + throw new Exception("Unknown field in top level key line :\n " + line); + } + } + } - if (keyValue.Length!=2) - throw new Exception("Invalid info field: "+field); + private void AddInfoField(string line) + { + //##IAE_INFO= + line = line.Substring(12);//removing ##IAE_INFO=< + line = line.Substring(0, line.Length - 1);//removing the last '>' - var key = keyValue[0]; - var value = keyValue[1]; + var fields = line.OptimizedSplit(','); + string info = null, type = null, json = null; - switch (key) - { - case "INFO": - info = value; - break; - case "Type": - type = value; - break; - case "JSON": - json = value; - break; - default: - throw new Exception("Unknown field in info field line :\n" + line); + foreach (var field in fields) + { + (string key, string value) = field.OptimizedKeyValue(); + if (value == null) throw new Exception("Invalid info field: " + field); - } - } + switch (key) + { + case "INFO": + info = value; + break; + case "Type": + type = value; + break; + case "JSON": + json = value; + break; + default: + throw new Exception("Unknown field in info field line :\n" + line); + } + } - if (type==null || info==null || json ==null) - throw new Exception("Missing mandatory field from IAE_INFO:\n"+line); + if (type == null || info == null || json == null) + throw new InvalidDataException("Missing mandatory field from IAE_INFO:\n" + line); - switch (type) - { - case "String": - _desiredStringFields[info] = json; - break; - case "Number": - _desiredNumberFields[info] = json; - break; - case "Boolean": - _desiredBoolFields[info] = json; - break; - default: - throw new Exception("Unsupported data type: "+type+ " in:\n"+ line); - } - - } + switch (type) + { + case "String": + _desiredStringFields[info] = json; + break; + case "Number": + _desiredNumberFields[info] = json; + break; + case "Boolean": + _desiredBoolFields[info] = json; + break; + default: + throw new Exception("Unsupported data type: " + type + " in:\n" + line); + } + } - private void ParseHeaderLine(string line) + private void ParseHeaderLine(string line) { //##IAE_TOP= if (line.StartsWith("##IAE_TOP=")) GetTopLevelKey(line); @@ -195,7 +185,7 @@ private void ParseHeaderLine(string line) private List ExtractCustomItems(string vcfline) { if (vcfline == null) return null; - var splitLine = vcfline.Split('\t');// we don't care about the many fields after info field + var splitLine = vcfline.OptimizedSplit('\t');// we don't care about the many fields after info field if (splitLine.Length < 8) return null; @@ -206,7 +196,7 @@ private List ExtractCustomItems(string vcfline) var position = int.Parse(splitLine[VcfCommon.PosIndex]);//we have to get it from RSPOS in info var refAllele = splitLine[VcfCommon.RefIndex]; - var altAlleles = splitLine[VcfCommon.AltIndex].Split(','); + var altAlleles = splitLine[VcfCommon.AltIndex].OptimizedSplit(','); var id = splitLine[VcfCommon.IdIndex]; var infoFields = splitLine[VcfCommon.InfoIndex]; @@ -231,7 +221,7 @@ private List ExtractCustomItems(string vcfline) numberValues[keyValue.Key] = keyValue.Value[i]; } - var boolValues = _boolValues.Select(value => value.Split(',')[i]).ToList(); + var boolValues = _boolValues.Select(value => value.OptimizedSplit(',')[i]).ToList(); _customItemList.Add(new CustomItem( chromosome, @@ -248,32 +238,29 @@ private List ExtractCustomItems(string vcfline) return _customItemList; } - private void ParseInfoField(string infoFields) - { - // 1       69345   COSM911918      C       A       .       .       GENE=OR4F5;STRAND=+;CDS=c.255C>A;AA=p.I85I;CNT=1;EX_TARGET - foreach (var infoField in infoFields.Split(';')) - { - var keyValue = infoField.Split('='); - var key = keyValue[0]; - if (keyValue.Length == 1) - { - // undefined boolean keys will be skipped - if (_desiredBoolFields.ContainsKey(key)) - _boolValues.Add(_desiredBoolFields[key]); - continue; - } - - var value = keyValue[1]; + private void ParseInfoField(string infoFields) + { + // 1       69345   COSM911918      C       A       .       .       GENE=OR4F5;STRAND=+;CDS=c.255C>A;AA=p.I85I;CNT=1;EX_TARGET + foreach (string infoField in infoFields.OptimizedSplit(';')) + { + (string key, string value) = infoField.OptimizedKeyValue(); - // undefined string keys will be skipped - if (_desiredStringFields.ContainsKey(key)) - _stringValues[_desiredStringFields[key]] = value.Split(',').ToList(); + if (value == null) + { + // undefined boolean keys will be skipped + if (_desiredBoolFields.ContainsKey(key)) + _boolValues.Add(_desiredBoolFields[key]); + continue; + } - // undefined number keys will be skipped - if (_desiredNumberFields.ContainsKey(key)) - _numberValues[_desiredNumberFields[key]] = value.Split(',').Select(double.Parse).ToList(); - } - } + // undefined string keys will be skipped + if (_desiredStringFields.ContainsKey(key)) + _stringValues[_desiredStringFields[key]] = value.OptimizedSplit(',').ToList(); - } + // undefined number keys will be skipped + if (_desiredNumberFields.ContainsKey(key)) + _numberValues[_desiredNumberFields[key]] = value.OptimizedSplit(',').Select(double.Parse).ToList(); + } + } + } } diff --git a/SAUtils/InputFileParsers/CustomInterval/CustomIntervalParser.cs b/SAUtils/InputFileParsers/CustomInterval/CustomIntervalParser.cs index 8a1dd947..b16c18b0 100644 --- a/SAUtils/InputFileParsers/CustomInterval/CustomIntervalParser.cs +++ b/SAUtils/InputFileParsers/CustomInterval/CustomIntervalParser.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using Compression.Utilities; +using OptimizedCore; using VariantAnnotation.Interface.Sequence; namespace SAUtils.InputFileParsers.CustomInterval @@ -48,7 +49,7 @@ private void ReadHeader() { // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; - if (line.StartsWith("#")) + if (line.OptimizedStartsWith('#')) { ParseHeaderLine(line); } @@ -71,7 +72,7 @@ private void ReadHeader() { // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; var customInterval = ExtractCustomInterval(line); if (customInterval == null) continue; @@ -84,7 +85,7 @@ private void ReadHeader() private DataStructures.CustomInterval ExtractCustomInterval(string bedLine) { if (bedLine == null) return null; - var bedFields = bedLine.Split('\t'); + var bedFields = bedLine.OptimizedSplit('\t'); if (bedFields.Length < BedCommon.MinNoOfFields) throw new Exception("Bed file line must contain at least"+ BedCommon.MinNoOfFields+" fields. Current line:\n "+ bedLine); @@ -121,7 +122,7 @@ private DataStructures.CustomInterval ExtractCustomInterval(string bedLine) private void ParseInfoField(string infoFieldsLine) { // OR4F5;0.0;3.60208899915;Some_evidence_of_constraint - var infoFields = infoFieldsLine.Split(';'); + var infoFields = infoFieldsLine.OptimizedSplit(';'); for (int i = 0; i < infoFields.Length; i++) { var key = _fieldIndex[i]; @@ -151,79 +152,68 @@ private void GetTopLevelKey(string line) line = line.Substring(11);//removing ##IAE_TOP=< line = line.Substring(0, line.Length - 1);//removing the last '>' - var keyField = line.Split('='); - var key = keyField[0]; - var val = keyField[1]; - - switch (key) + (string key, string value) = line.OptimizedKeyValue(); + + switch (key) { case "TYPE": - KeyName = val; + KeyName = value; break; default: throw new Exception("Unknown field in top level key line :\n " + line); - - } - - } - - private void AddInfoField(string line) - { - //##IAE_INFO= - line = line.Substring(12);//removing ##IAE_INFO=< - line = line.Substring(0, line.Length - 1);//removing the last '>' - - var fields = line.Split(','); - - string type = null, json = null; - int ? index = null; - - foreach (var field in fields) - { - var keyValue = field.Split('='); - - if (keyValue.Length != 2) - throw new Exception("Invalid info field: " + field); - - var key = keyValue[0].ToUpper(); - var value = keyValue[1]; - - switch (key) - { - case "INFO": - break; - case "TYPE": - type = value; - break; - case "JSON": - json = value; - break; - case "INDEX": - index = Convert.ToInt16(value); - break; - default: - throw new Exception("Unknown field in info field line :\n" + line); - - } - } - - if (type == null || json == null || index ==null ) - throw new Exception("Missing mandatory field from IAE_INFO:\n" + line); - - if (type.ToLower() == "string") - { - _stringFields.Add(json); - } - else - { - _nonstringFields.Add(json); - } - if (_fieldIndex.ContainsKey(index.Value)) - { - throw new Exception("duplicate index:\n" + line); - } - _fieldIndex[index.Value] = json; - } - - } + } + } + + private void AddInfoField(string line) + { + //##IAE_INFO= + line = line.Substring(12);//removing ##IAE_INFO=< + line = line.Substring(0, line.Length - 1);//removing the last '>' + + var fields = line.OptimizedSplit(','); + + string type = null, json = null; + int? index = null; + + foreach (var field in fields) + { + (string key, string value) = field.OptimizedKeyValue(); + if (value == null) throw new InvalidDataException("Invalid info field: " + field); + + switch (key.ToUpper()) + { + case "INFO": + break; + case "TYPE": + type = value; + break; + case "JSON": + json = value; + break; + case "INDEX": + index = Convert.ToInt16(value); + break; + default: + throw new Exception("Unknown field in info field line :\n" + line); + } + } + + if (type == null || json == null || index == null) + throw new Exception("Missing mandatory field from IAE_INFO:\n" + line); + + if (type.ToLower() == "string") + { + _stringFields.Add(json); + } + else + { + _nonstringFields.Add(json); + } + if (_fieldIndex.ContainsKey(index.Value)) + { + throw new Exception("duplicate index:\n" + line); + } + _fieldIndex[index.Value] = json; + } + } } diff --git a/SAUtils/InputFileParsers/DGV/DgvReader.cs b/SAUtils/InputFileParsers/DGV/DgvReader.cs index db6ac958..03ab05e8 100644 --- a/SAUtils/InputFileParsers/DGV/DgvReader.cs +++ b/SAUtils/InputFileParsers/DGV/DgvReader.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using Compression.Utilities; +using OptimizedCore; using SAUtils.DataStructures; using VariantAnnotation.Interface.Sequence; @@ -27,7 +28,7 @@ public DgvReader(FileInfo dgvFileInfo, IDictionary refChrom /// public static DgvItem ExtractDgvItem(string line, IDictionary refChromDict) { - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols.Length < 8) return null; var id = cols[0]; diff --git a/SAUtils/InputFileParsers/DataSourceVersionReader.cs b/SAUtils/InputFileParsers/DataSourceVersionReader.cs index 47cfd992..f058f5ce 100644 --- a/SAUtils/InputFileParsers/DataSourceVersionReader.cs +++ b/SAUtils/InputFileParsers/DataSourceVersionReader.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using OptimizedCore; using VariantAnnotation.Providers; using VariantAnnotation.Utilities; @@ -63,22 +64,22 @@ public DataSourceVersion GetVersion() while ((line = _reader.ReadLine()) != null) { - var words = line.Split('='); - if (words.Length < 2) continue; + (string key, string value) = line.OptimizedKeyValue(); + if (key == null || value == null) continue; - switch (words[0]) + switch (key) { case "NAME": - name = words[1]; + name = value; break; case "VERSION": - version = words[1]; + version = value; break; case "DATE": - date = words[1]; + date = value; break; case "DESCRIPTION": - description = words[1]; + description = value; break; } } diff --git a/SAUtils/InputFileParsers/DbSnp/DbSnpReader.cs b/SAUtils/InputFileParsers/DbSnp/DbSnpReader.cs index d6bd34d2..4d1e8ed2 100644 --- a/SAUtils/InputFileParsers/DbSnp/DbSnpReader.cs +++ b/SAUtils/InputFileParsers/DbSnp/DbSnpReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using OptimizedCore; using SAUtils.DataStructures; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Sequence; @@ -33,7 +34,7 @@ public IEnumerable GetDbSnpItems() // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; // Skip comments. - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; var dbSnpItems = ExtractItem(line); if (dbSnpItems == null || dbSnpItems.Count == 0) continue; foreach (var dbSnpItem in dbSnpItems) @@ -52,7 +53,7 @@ public IEnumerable GetDbSnpItems() /// public List ExtractItem(string vcfline) { - var splitLine = vcfline.Split('\t'); + var splitLine = vcfline.OptimizedSplit('\t'); if (splitLine.Length < 8) return null; var chromosomeName = splitLine[VcfCommon.ChromIndex]; @@ -63,7 +64,7 @@ public List ExtractItem(string vcfline) var position = int.Parse(splitLine[VcfCommon.PosIndex]); var dbSnpId = Convert.ToInt64(splitLine[VcfCommon.IdIndex].Substring(2)); var refAllele = splitLine[VcfCommon.RefIndex]; - var altAlleles = splitLine[VcfCommon.AltIndex].Split(','); + var altAlleles = splitLine[VcfCommon.AltIndex].OptimizedSplit(','); var infoField = splitLine[VcfCommon.InfoIndex]; var alleleFrequencies = GetAlleleFrequencies(infoField, refAllele, altAlleles); @@ -72,41 +73,41 @@ public List ExtractItem(string vcfline) } - private static Dictionary GetAlleleFrequencies(string infoField, string refAllele, string[] altAlleles) - { - var freqDict = new Dictionary {[refAllele] = double.MinValue}; - - foreach (var altAllele in altAlleles) - { - freqDict[altAllele] = double.MinValue; - } - - if (infoField.Trim() == ".") return freqDict; - - // for now we also want to disregard anything other than SNVs - var allSnv = refAllele.Length == 1 && altAlleles.All(altAllele => altAllele.Length == 1); - if (! allSnv) return freqDict; - - // return if there are no freq information - if (!infoField.Contains("CAF=")) - return freqDict; - - foreach (var info in infoField.Split(';')) - { - if (!info.StartsWith("CAF=")) continue; - var alleleFrequencies = info.Split('=')[1].Split(','); - - freqDict[refAllele] = GetFrequency(alleleFrequencies[0]); - - for (int i = 1; i < alleleFrequencies.Length; i++) - freqDict[altAlleles[i - 1]] = GetFrequency(alleleFrequencies[i]); - break; - } - return freqDict; - - } + private static Dictionary GetAlleleFrequencies(string infoField, string refAllele, string[] altAlleles) + { + var freqDict = new Dictionary { [refAllele] = double.MinValue }; + + foreach (var altAllele in altAlleles) + { + freqDict[altAllele] = double.MinValue; + } + + if (infoField.Trim() == ".") return freqDict; + + // for now we also want to disregard anything other than SNVs + var allSnv = refAllele.Length == 1 && altAlleles.All(altAllele => altAllele.Length == 1); + if (!allSnv) return freqDict; + + // return if there are no freq information + if (!infoField.Contains("CAF=")) + return freqDict; + + foreach (var info in infoField.OptimizedSplit(';')) + { + if (!info.StartsWith("CAF=")) continue; + var alleleFrequencies = info.OptimizedKeyValue().Value.OptimizedSplit(','); + + freqDict[refAllele] = GetFrequency(alleleFrequencies[0]); + + for (int i = 1; i < alleleFrequencies.Length; i++) + freqDict[altAlleles[i - 1]] = GetFrequency(alleleFrequencies[i]); + break; + } + + return freqDict; + } - private static double GetFrequency(string alleleFrequency) + private static double GetFrequency(string alleleFrequency) { return alleleFrequency == "." || alleleFrequency == "0" ? double.MinValue : Convert.ToDouble(alleleFrequency); } diff --git a/SAUtils/InputFileParsers/EVS/EvsReader.cs b/SAUtils/InputFileParsers/EVS/EvsReader.cs index 90ae82ff..cf243f9b 100644 --- a/SAUtils/InputFileParsers/EVS/EvsReader.cs +++ b/SAUtils/InputFileParsers/EVS/EvsReader.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.IO; using System.Linq; +using OptimizedCore; using SAUtils.DataStructures; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Sequence; @@ -47,7 +48,7 @@ public IEnumerable GetEvsItems() // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; // Skip comments. - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; var evsItemsList = ExtractItems(line); if (evsItemsList == null) continue; foreach (var evsItem in evsItemsList) @@ -72,7 +73,7 @@ internal List ExtractItems(string vcfline) var position = int.Parse(splitLine[VcfCommon.PosIndex]);//we have to get it from RSPOS in info var rsId = splitLine[VcfCommon.IdIndex]; var refAllele = splitLine[VcfCommon.RefIndex]; - var altAlleles = splitLine[VcfCommon.AltIndex].Split(','); + var altAlleles = splitLine[VcfCommon.AltIndex].OptimizedSplit(','); var infoFields = splitLine[VcfCommon.InfoIndex]; //return null if the position is -1. This happens for entries in GRCh38 @@ -98,47 +99,41 @@ internal List ExtractItems(string vcfline) ); } return evsItemsList; - } - - private void ParseInfoField(string infoFields) - { - if (infoFields == "" || infoFields == ".") return; - - var infoItems = infoFields.Split(';'); - foreach (var infoItem in infoItems) - { - var infoKeyValue = infoItem.Split('='); - if (infoKeyValue.Length == 2)//sanity check - { - var key = infoKeyValue[0]; - var value = infoKeyValue[1]; - - SetInfoField(key, value); - } - - } - - } - - private void SetInfoField(string vcfId, string value) + } + + private void ParseInfoField(string infoFields) + { + if (infoFields == "" || infoFields == ".") return; + var infoItems = infoFields.OptimizedSplit(';'); + + foreach (string infoItem in infoItems) + { + (string key, string value) = infoItem.OptimizedKeyValue(); + + //sanity check + if (value != null) SetInfoField(key, value); + } + } + + private void SetInfoField(string vcfId, string value) { switch (vcfId) { case "EA_AC": - var europeanAlleleCounts = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + var europeanAlleleCounts = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); var totalEurAlleleCount = europeanAlleleCounts.Sum(); _europeanFrequencies = europeanAlleleCounts.Select(val => 1.0*val/totalEurAlleleCount).ToArray(); break; case "AA_AC": - var africanAlleleCounts = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + var africanAlleleCounts = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); var totalAfrAlleleCount = africanAlleleCounts.Sum(); _africanFrequencies = africanAlleleCounts.Select(val => 1.0*val/totalAfrAlleleCount).ToArray(); break; case "TAC": - var alleleCounts = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + var alleleCounts = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); var totalAlleleCount = alleleCounts.Sum(); _allFrequencies = alleleCounts.Select(val => 1.0*val/totalAlleleCount).ToArray(); @@ -147,7 +142,7 @@ private void SetInfoField(string vcfId, string value) _coverage = value; break; case "GTC": - int count = value.Split(',').Sum(Convert.ToInt32); + int count = value.OptimizedSplit(',').Sum(Convert.ToInt32); _numSamples = count.ToString(CultureInfo.InvariantCulture); break; } diff --git a/SAUtils/InputFileParsers/ExAc/ExAcReader.cs b/SAUtils/InputFileParsers/ExAc/ExAcReader.cs index 73793b8d..88b30d7a 100644 --- a/SAUtils/InputFileParsers/ExAc/ExAcReader.cs +++ b/SAUtils/InputFileParsers/ExAc/ExAcReader.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Compression.Utilities; +using OptimizedCore; using SAUtils.DataStructures; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Sequence; @@ -76,7 +77,7 @@ public IEnumerable GetExacItems() // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; // Skip comments. - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; var exacItemsList = ExtractItems(line); if (exacItemsList == null) continue; foreach (var exacItem in exacItemsList) @@ -96,9 +97,9 @@ public IEnumerable GetExacItems() public List ExtractItems(string vcfline) { if (vcfline == null) return null; - var splitLine = vcfline.Split( '\t');// we don't care about the many fields after info field - - if (splitLine.Length < 8) return null; + var splitLine = vcfline.OptimizedSplit('\t'); + + if (splitLine.Length < 8) return null; Clear(); @@ -108,7 +109,7 @@ public List ExtractItems(string vcfline) var chrom = _refChromDict[chromosome]; var position = int.Parse(splitLine[VcfCommon.PosIndex]);//we have to get it from RSPOS in info var refAllele = splitLine[VcfCommon.RefIndex]; - var altAlleles = splitLine[VcfCommon.AltIndex].Split(','); + var altAlleles = splitLine[VcfCommon.AltIndex].OptimizedSplit(','); var infoFields = splitLine[VcfCommon.InfoIndex]; // parses the info fields and extract frequencies, coverage, num samples. @@ -143,67 +144,63 @@ public List ExtractItems(string vcfline) return alleleCounts[i]; } - /// - /// split up the info field and extract information from each of them. - /// - /// - private void ParseInfoField(string infoFields) - { - if (infoFields == "" || infoFields == ".") return; - - var infoItems = infoFields.Split(';'); - foreach (var infoItem in infoItems) - { - var infoKeyValue = infoItem.Split('='); - if (infoKeyValue.Length == 2)//sanity check - { - var key = infoKeyValue[0]; - var value = infoKeyValue[1]; - - SetInfoField(key, value); - } - } - } - - /// - /// Get a key value pair and using the key, set appropriate values - /// - /// - /// - private void SetInfoField(string vcfId, string value) + /// + /// split up the info field and extract information from each of them. + /// + /// + private void ParseInfoField(string infoFields) + { + if (infoFields == "" || infoFields == ".") return; + var infoItems = infoFields.OptimizedSplit(';'); + + foreach (string infoItem in infoItems) + { + (string key, string value) = infoItem.OptimizedKeyValue(); + + // sanity check + if (value != null) SetInfoField(key, value); + } + } + + /// + /// Get a key value pair and using the key, set appropriate values + /// + /// + /// + private void SetInfoField(string vcfId, string value) { switch (vcfId) { case "AC_Adj": - _acAdj = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acAdj = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_AFR": - _acAfr = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acAfr = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_AMR": - _acAmr = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acAmr = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_EAS": - _acEas = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acEas = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_FIN": - _acFin = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acFin = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_NFE": - _acNfe = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acNfe = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_OTH": - _acOth = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acOth = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_SAS": - _acSas = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acSas = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AN_Adj": diff --git a/SAUtils/InputFileParsers/GnomadReader.cs b/SAUtils/InputFileParsers/GnomadReader.cs index 1e25e1d1..52d63698 100644 --- a/SAUtils/InputFileParsers/GnomadReader.cs +++ b/SAUtils/InputFileParsers/GnomadReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using OptimizedCore; using SAUtils.DataStructures; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Sequence; @@ -106,7 +107,7 @@ public IEnumerable GetGnomadItems() // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; // Skip comments. - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; var gnomadItemsList = ExtractItems(line); if (gnomadItemsList == null) continue; foreach (var gnomadItem in gnomadItemsList) @@ -126,9 +127,9 @@ public IEnumerable GetGnomadItems() private List ExtractItems(string vcfline) { if (vcfline == null) return null; - var splitLine = vcfline.Split( '\t');// we don't care about the many fields after info field - - if (splitLine.Length < 8) return null; + var splitLine = vcfline.OptimizedSplit('\t'); + + if (splitLine.Length < 8) return null; Clear(); @@ -138,7 +139,7 @@ private List ExtractItems(string vcfline) var chrom = _refChromDict[chromosome]; var position = int.Parse(splitLine[VcfCommon.PosIndex]);//we have to get it from RSPOS in info var refAllele = splitLine[VcfCommon.RefIndex]; - var altAlleles = splitLine[VcfCommon.AltIndex].Split(','); + var altAlleles = splitLine[VcfCommon.AltIndex].OptimizedSplit(','); var filters = splitLine[VcfCommon.FilterIndex]; var infoFields = splitLine[VcfCommon.InfoIndex]; @@ -194,20 +195,16 @@ private List ExtractItems(string vcfline) /// private void ParseInfoField(string infoFields) { - if (infoFields == "" || infoFields == ".") return; + if (infoFields == "" || infoFields == ".") return; + var infoItems = infoFields.OptimizedSplit(';'); - var infoItems = infoFields.Split(';'); - foreach (var infoItem in infoItems) - { - var infoKeyValue = infoItem.Split('='); - if (infoKeyValue.Length == 2)//sanity check - { - var key = infoKeyValue[0]; - var value = infoKeyValue[1]; + foreach (string infoItem in infoItems) + { + (string key, string value) = infoItem.OptimizedKeyValue(); - SetInfoField(key, value); - } - } + // sanity check + if (value != null) SetInfoField(key, value); + } } /// @@ -220,39 +217,39 @@ private void SetInfoField(string vcfId, string value) switch (vcfId) { case "AC": - _acAll = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acAll = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_AFR": - _acAfr = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acAfr = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_AMR": - _acAmr = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acAmr = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_EAS": - _acEas = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acEas = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_FIN": - _acFin = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acFin = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_NFE": - _acNfe = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acNfe = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_OTH": - _acOth = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acOth = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_ASJ": - _acAsj = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acAsj = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AC_SAS": - _acSas = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _acSas = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AN": @@ -292,39 +289,39 @@ private void SetInfoField(string vcfId, string value) break; case "Hom": - _hcAll = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _hcAll = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "Hom_AFR": - _hcAfr = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _hcAfr = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "Hom_AMR": - _hcAmr = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _hcAmr = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "Hom_EAS": - _hcEas = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _hcEas = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "Hom_FIN": - _hcFin = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _hcFin = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "Hom_NFE": - _hcNfe = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _hcNfe = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "Hom_OTH": - _hcOth = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _hcOth = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "Hom_ASJ": - _hcAsj = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _hcAsj = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "Hom_SAS": - _hcSas = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _hcSas = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "DP": diff --git a/SAUtils/InputFileParsers/IntermediateAnnotation/GeneTsvReader.cs b/SAUtils/InputFileParsers/IntermediateAnnotation/GeneTsvReader.cs index 7f6fba56..f5cace50 100644 --- a/SAUtils/InputFileParsers/IntermediateAnnotation/GeneTsvReader.cs +++ b/SAUtils/InputFileParsers/IntermediateAnnotation/GeneTsvReader.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Compression.Utilities; +using OptimizedCore; using SAUtils.DataStructures; using SAUtils.Interface; using VariantAnnotation.IO; @@ -67,7 +68,7 @@ public IEnumerable GetItems() //getting to the chromosome while ((line = _reader.ReadLine()) != null) { - if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) continue; + if (string.IsNullOrWhiteSpace(line) || line.OptimizedStartsWith('#')) continue; var annotationItem = ExtractItem(line); if (annotationItem == null) continue; @@ -80,7 +81,7 @@ public IEnumerable GetItems() private IAnnotatedGene ExtractItem(string line) { - var columns = line.Split('\t'); + var columns = line.OptimizedSplit('\t'); if (columns.Length < MinNoOfColumns) throw new InvalidDataException("Line contains too few columns:\n" + line); @@ -91,11 +92,8 @@ private IAnnotatedGene ExtractItem(string line) private void ParseHeaderLine(string line) { - var words = line.Split('='); - if (words.Length < 2) return; - - var key = words[0]; - var value = words[1]; + (string key, string value) = line.OptimizedKeyValue(); + if (key == null || value == null) return; switch (key) { diff --git a/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelIntervalTsvReader.cs b/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelIntervalTsvReader.cs index 15d7a7ab..bbd6c978 100644 --- a/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelIntervalTsvReader.cs +++ b/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelIntervalTsvReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using Compression.Utilities; +using OptimizedCore; using SAUtils.DataStructures; using SAUtils.Interface; using VariantAnnotation.Interface.SA; @@ -63,7 +64,7 @@ private IntervalAnnotationHeader ReadHeader(StreamReader reader) { // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; - if (!line.StartsWith("#")) break; + if (!line.OptimizedStartsWith('#')) break; ParseHeaderLine(line); } @@ -93,7 +94,7 @@ public IEnumerable GetItems(string refName) //getting to the chromosome while ((line = reader.ReadLine()) != null) { - if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) continue; + if (string.IsNullOrWhiteSpace(line) || line.OptimizedStartsWith('#')) continue; // finding desired chromosome. We need this because the GetLocation for GZipStream may return a position a few lines before the start of the chromosome if (line.StartsWith(refName + "\t")) break; } @@ -115,7 +116,7 @@ public IEnumerable GetItems(string refName) private ISupplementaryInterval ExtractItem(string line) { - var columns = line.Split('\t'); + var columns = line.OptimizedSplit('\t'); if (columns.Length < MinNoOfColumns) throw new InvalidDataException("Line contains too few columns:\n" + line); @@ -129,11 +130,8 @@ private ISupplementaryInterval ExtractItem(string line) private void ParseHeaderLine(string line) { - var words = line.Split('='); - if (words.Length < 2) return; - - var key = words[0]; - var value = words[1]; + (string key, string value) = line.OptimizedKeyValue(); + if (key == null || value == null) return; switch (key) { diff --git a/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs b/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs index 64dc599c..5e870727 100644 --- a/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs +++ b/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using Compression.Utilities; +using OptimizedCore; using SAUtils.DataStructures; using SAUtils.Interface; using VariantAnnotation.Utilities; @@ -66,7 +67,7 @@ private SmallAnnotationHeader ReadHeader(StreamReader reader) // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; - if (!line.StartsWith("#")) break; + if (!line.OptimizedStartsWith('#')) break; ParseHeaderLine(line); } @@ -93,7 +94,7 @@ public IEnumerable GetItems(string refName) string line; while ((line = reader.ReadLine()) != null) { - if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) continue; + if (string.IsNullOrWhiteSpace(line) || line.OptimizedStartsWith('#')) continue; // finding desired chromosome. We need this because the GetLocation for GZipStream may return a position a few lines before the start of the chromosome if (line.StartsWith(refName + "\t")) break; } @@ -126,7 +127,7 @@ public IEnumerable GetItems(string refName) private InterimSaItem ExtractItem(string line) { - var columns = line.Split('\t'); + var columns = line.OptimizedSplit('\t'); if ( columns.Length < MinNoOfColumns) throw new InvalidDataException("Line contains too few columns:\n"+line); @@ -150,13 +151,10 @@ private InterimSaItem ExtractItem(string line) private void ParseHeaderLine(string line) { - var words = line.Split('='); - if (words.Length < 2) return; + (string key, string value) = line.OptimizedKeyValue(); + if (key == null || value == null) return; - var key = words[0]; - var value = words[1]; - - switch (key) + switch (key) { case "#name": _name = value; diff --git a/SAUtils/InputFileParsers/IntermediateAnnotation/SaMiscellaniesReader.cs b/SAUtils/InputFileParsers/IntermediateAnnotation/SaMiscellaniesReader.cs index 26a39afc..9cbc89e6 100644 --- a/SAUtils/InputFileParsers/IntermediateAnnotation/SaMiscellaniesReader.cs +++ b/SAUtils/InputFileParsers/IntermediateAnnotation/SaMiscellaniesReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using Compression.Utilities; +using OptimizedCore; using SAUtils.DataStructures; using SAUtils.Interface; using VariantAnnotation.Utilities; @@ -39,7 +40,7 @@ public IEnumerable GetItems(string refName) string line; while ((line = reader.ReadLine()) != null) { - if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) continue; + if (string.IsNullOrWhiteSpace(line) || line.OptimizedStartsWith('#')) continue; // finding desired chromosome. We need this because the GetLocation for GZipStream may return a position a few lines before the start of the chromosome if (line.StartsWith(refName + "\t")) break; } @@ -71,7 +72,7 @@ public IEnumerable GetItems(string refName) private static SaMiscellanies ExtractItem(string line) { - var columns = line.Split('\t'); + var columns = line.OptimizedSplit('\t'); return columns.Length < 3 ? null : new SaMiscellanies(InterimSaCommon.RefMinorTag, columns[0], Convert.ToInt32(columns[1]), columns[2]); } diff --git a/SAUtils/InputFileParsers/MitoMAP/MitoMapSvReader.cs b/SAUtils/InputFileParsers/MitoMAP/MitoMapSvReader.cs index 527bde61..4d2c7ca0 100644 --- a/SAUtils/InputFileParsers/MitoMAP/MitoMapSvReader.cs +++ b/SAUtils/InputFileParsers/MitoMAP/MitoMapSvReader.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.RegularExpressions; using ErrorHandling.Exceptions; +using OptimizedCore; using SAUtils.DataStructures; using SAUtils.InputFileParsers.ClinVar; using VariantAnnotation.Interface.Positions; @@ -58,7 +59,7 @@ private IEnumerable GetMitoMapItems() continue; } // last item - if (line.StartsWith("[") && line.EndsWith("]],")) isDataLine = false; + if (line.OptimizedStartsWith('[') && line.EndsWith("]],")) isDataLine = false; foreach (var supplementaryIntervalItem in ParseLine(line, _dataType)) { @@ -72,7 +73,7 @@ private IEnumerable GetMitoMapItems() private List ParseLine(string line, string dataType) { // line validation - if (!(line.StartsWith("[") && line.EndsWith("],"))) + if (!(line.OptimizedStartsWith('[') && line.EndsWith("],"))) throw new InvalidFileFormatException($"Data line doesn't start with \"[\" or end with \"],\": {line}"); var info = line.TrimEnd(',').TrimEnd(']').Trim('[', ']').Split("\",\"").Select(x => x.Trim('"')).ToList(); return dataType == MitoMapDataTypes.MitoMapInsertionsSimple ? ExtractSvItemFromSimpleInsertions(info) : ExtractSvItemFromDeletionsSingle(info); @@ -80,7 +81,7 @@ private List ParseLine(string line, string dataType) private List ExtractSvItemFromDeletionsSingle(List info) { - var junctions = info[0].Split(':').Select(int.Parse).ToList(); + var junctions = info[0].OptimizedSplit(':').Select(int.Parse).ToList(); var start = junctions[0] + 1; var end = junctions[1] - 1; if (end < start) diff --git a/SAUtils/InputFileParsers/MitoMAP/MitoMapVariantReader.cs b/SAUtils/InputFileParsers/MitoMAP/MitoMapVariantReader.cs index 6c257653..4f524ea3 100644 --- a/SAUtils/InputFileParsers/MitoMAP/MitoMapVariantReader.cs +++ b/SAUtils/InputFileParsers/MitoMAP/MitoMapVariantReader.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.RegularExpressions; using ErrorHandling.Exceptions; +using OptimizedCore; using SAUtils.DataStructures; using SAUtils.InputFileParsers.ClinVar; using VariantAnnotation.Providers; @@ -78,7 +79,7 @@ private IEnumerable GetMitoMapItems() continue; } // last item - if (line.StartsWith("[") && line.EndsWith("]],")) isDataLine = false; + if (line.OptimizedStartsWith('[') && line.EndsWith("]],")) isDataLine = false; foreach (var mitoMapMutItem in ParseLine(line, _dataType)) { @@ -93,7 +94,7 @@ private IEnumerable GetMitoMapItems() private List ParseLine(string line, string dataType) { // line validation - if (!(line.StartsWith("[") && line.EndsWith("],"))) + if (!(line.OptimizedStartsWith('[') && line.EndsWith("],"))) throw new InvalidFileFormatException($"Data line doesn't start with \"[\" or end with \"],\": {line}"); /* example lines ["582","MT-TF","Mitochondrial myopathy","T582C","tRNA Phe","-","+","Reported","72.90% ","0","2"], @@ -112,7 +113,7 @@ private List ParseLine(string line, string dataType) private List ExtractVariantItemFromDeletionsSingle(List info) { - var junctions = info[0].Split(':').Select(int.Parse).ToList(); + var junctions = info[0].OptimizedSplit(':').Select(int.Parse).ToList(); var start = junctions[0] + 1; var end = junctions[1] - 1; if (end < start) @@ -196,7 +197,7 @@ private List ExtracVariantItem(List info, int[] fields) } // there may be multiple alt alleles concatenated by ";" - internal static IEnumerable GetAltAlleles(string rawAltAllele) => rawAltAllele.Split(";").Select(DegenerateBaseUtilities.GetAllPossibleSequences).SelectMany(x => x); + internal static IEnumerable GetAltAlleles(string rawAltAllele) => rawAltAllele.OptimizedSplit(';').Select(DegenerateBaseUtilities.GetAllPossibleSequences).SelectMany(x => x); private static bool DescribedAsDuplicatedRecord(string mitomapDiseaseString) diff --git a/SAUtils/InputFileParsers/OneKGen/OneKGenReader.cs b/SAUtils/InputFileParsers/OneKGen/OneKGenReader.cs index b551f351..ad7acd5e 100644 --- a/SAUtils/InputFileParsers/OneKGen/OneKGenReader.cs +++ b/SAUtils/InputFileParsers/OneKGen/OneKGenReader.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Compression.Utilities; +using OptimizedCore; using SAUtils.DataStructures; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Sequence; @@ -99,7 +100,7 @@ public IEnumerable GetOneKGenItems() // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; // Skip comments. - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; var oneKGenItemsList = ExtractItems(line); if (oneKGenItemsList == null) continue; foreach (var oneKGenItem in oneKGenItemsList) @@ -124,11 +125,11 @@ internal List ExtractItems(string vcfline) var position = int.Parse(splitLine[VcfCommon.PosIndex]);//we have to get it from RSPOS in info var rsId = splitLine[VcfCommon.IdIndex]; _refAllele = splitLine[VcfCommon.RefIndex]; - _altAlleles = splitLine[VcfCommon.AltIndex].Split(','); + _altAlleles = splitLine[VcfCommon.AltIndex].OptimizedSplit(','); var infoFields = splitLine[VcfCommon.InfoIndex]; // parses the info fields and extract frequencies, ancestral allele, allele counts, etc. - var hasSymbolicAllele = _altAlleles.Any(x => x.StartsWith("<") && x.EndsWith(">")); + var hasSymbolicAllele = _altAlleles.Any(x => x.OptimizedStartsWith('<') && x.OptimizedEndsWith('>')); if (hasSymbolicAllele) return null; // ReSharper disable once ConditionIsAlwaysTrueOrFalse @@ -171,29 +172,23 @@ internal List ExtractItems(string vcfline) if (alleleCounts == null) return null; if (i >= alleleCounts.Length) return null; return alleleCounts[i]; - } - - private void ParseInfoField(string infoFields, bool hasSymbolicAllele) - { - if (infoFields == "" || infoFields == ".") return; - - var infoItems = infoFields.Split(';'); - foreach (var infoItem in infoItems) - { - var infoKeyValue = infoItem.Split('='); - if (infoKeyValue.Length == 2)//sanity check - { - var key = infoKeyValue[0]; - var value = infoKeyValue[1]; - - SetInfoField(key, value, hasSymbolicAllele); - } - - } - - } - - private void SetInfoField(string vcfAfId, string value, bool hasSymbolicAllele) + } + + private void ParseInfoField(string infoFields, bool hasSymbolicAllele) + { + if (infoFields == "" || infoFields == ".") return; + var infoItems = infoFields.OptimizedSplit(';'); + + foreach (string infoItem in infoItems) + { + (string key, string value) = infoItem.OptimizedKeyValue(); + + // sanity check + if (value != null) SetInfoField(key, value, hasSymbolicAllele); + } + } + + private void SetInfoField(string vcfAfId, string value, bool hasSymbolicAllele) { switch (vcfAfId) { @@ -212,14 +207,14 @@ private void SetInfoField(string vcfAfId, string value, bool hasSymbolicAllele) case "CIEND": /*if (hasSymbolicAllele) { - var endBoundaries = value.Split(','); + var endBoundaries = value.OptimizedSplit(','); Tuple.Create(Convert.ToInt32(endBoundaries[0]), Convert.ToInt32(endBoundaries[1])); } break;*/ case "CIPOS": /*if (hasSymbolicAllele) { - var beginBoundaries = value.Split(','); + var beginBoundaries = value.OptimizedSplit(','); Tuple.Create(Convert.ToInt32(beginBoundaries[0]), Convert.ToInt32(beginBoundaries[1])); }*/ break; @@ -242,22 +237,22 @@ private void SetInfoField(string vcfAfId, string value, bool hasSymbolicAllele) _sasAlleleNumber = Convert.ToInt32(value); break; case "AC": - _allAlleleCounts = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _allAlleleCounts = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AMR_AC": - _amrAlleleCounts = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _amrAlleleCounts = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "AFR_AC": - _afrAlleleCounts = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _afrAlleleCounts = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "EUR_AC": - _eurAlleleCounts = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _eurAlleleCounts = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "EAS_AC": - _easAlleleCounts = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _easAlleleCounts = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; case "SAS_AC": - _sasAlleleCounts = value.Split(',').Select(val => Convert.ToInt32(val)).ToArray(); + _sasAlleleCounts = value.OptimizedSplit(',').Select(val => Convert.ToInt32(val)).ToArray(); break; } @@ -268,7 +263,7 @@ private static string GetAncestralAllele(string value) { if (value == "" || value == ".") return null; - var ancestralAllele = value.Split('|')[0]; + var ancestralAllele = value.OptimizedSplit('|')[0]; if (string.IsNullOrEmpty(ancestralAllele)) return null; return ancestralAllele.All(IsNucleotide) ? ancestralAllele : null; } diff --git a/SAUtils/InputFileParsers/OneKGen/oneKGenSvReader.cs b/SAUtils/InputFileParsers/OneKGen/oneKGenSvReader.cs index 89c5ff67..60521f08 100644 --- a/SAUtils/InputFileParsers/OneKGen/oneKGenSvReader.cs +++ b/SAUtils/InputFileParsers/OneKGen/oneKGenSvReader.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using Compression.Utilities; +using OptimizedCore; using SAUtils.DataStructures; using VariantAnnotation.Interface.Sequence; @@ -30,7 +31,7 @@ public IEnumerable GetOneKGenSvItems() // Skip empty lines. if (string.IsNullOrWhiteSpace(line)) continue; // Skip comments. - if (line.StartsWith("#")) continue; + if (line.OptimizedStartsWith('#')) continue; var oneKSvGenItem = ExtractOneKGenSvItem(line, _refNameDict); if (oneKSvGenItem == null ) continue; yield return oneKSvGenItem; @@ -41,7 +42,7 @@ public IEnumerable GetOneKGenSvItems() private static OneKGenItem ExtractOneKGenSvItem(string line, IDictionary refNameDict) { - var cols = line.Split('\t'); + var cols = line.OptimizedSplit('\t'); if (cols.Length < 8) return null; var id = cols[0]; diff --git a/SAUtils/InputFileParsers/TOPMed/TopMedReader.cs b/SAUtils/InputFileParsers/TOPMed/TopMedReader.cs index 033b88b6..a26fdb4d 100644 --- a/SAUtils/InputFileParsers/TOPMed/TopMedReader.cs +++ b/SAUtils/InputFileParsers/TOPMed/TopMedReader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using OptimizedCore; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Sequence; @@ -37,7 +38,7 @@ public IEnumerable GetGnomadItems() string line; while ((line = _reader.ReadLine()) != null) { - if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) continue; + if (string.IsNullOrWhiteSpace(line) || line.OptimizedStartsWith('#')) continue; var topMedItem = ExtractItems(line); if (topMedItem == null) continue; @@ -49,7 +50,7 @@ public IEnumerable GetGnomadItems() private TopMedItem ExtractItems(string vcfLine) { if (vcfLine == null) return null; - var splitLine = vcfLine.Split('\t'); + var splitLine = vcfLine.OptimizedSplit('\t'); if (splitLine.Length < 8) return null; @@ -85,19 +86,14 @@ private TopMedItem ExtractItems(string vcfLine) private void ParseInfoField(string infoFields) { if (infoFields == "" || infoFields == ".") return; + var infoItems = infoFields.OptimizedSplit(';'); - var infoItems = infoFields.Split(';'); - foreach (var infoItem in infoItems) + foreach (string infoItem in infoItems) { - var infoKeyValue = infoItem.Split('='); + (string key, string value) = infoItem.OptimizedKeyValue(); - if (infoKeyValue.Length == 2) - { - var key = infoKeyValue[0]; - var value = infoKeyValue[1]; - - SetInfoField(key, value); - } + // sanity check + if (value != null) SetInfoField(key, value); } } diff --git a/SAUtils/SAUtils.csproj b/SAUtils/SAUtils.csproj index 25e381b8..8223755e 100644 --- a/SAUtils/SAUtils.csproj +++ b/SAUtils/SAUtils.csproj @@ -12,9 +12,6 @@ latest - - - diff --git a/UnitTests/.exclude b/UnitTests/.exclude deleted file mode 100644 index e69de29b..00000000 diff --git a/UnitTests/Compression/FileHandling/BlockStreamTests.cs b/UnitTests/Compression/FileHandling/BlockStreamTests.cs index 79f951b2..cb1fd224 100644 --- a/UnitTests/Compression/FileHandling/BlockStreamTests.cs +++ b/UnitTests/Compression/FileHandling/BlockStreamTests.cs @@ -3,6 +3,7 @@ using System.IO.Compression; using System.Linq; using System.Security.Cryptography; +using System.Text; using Compression.Algorithms; using Compression.DataStructures; using Compression.FileHandling; @@ -34,7 +35,7 @@ public void BlockStream_EndToEnd() string expectedString = GetRandomString(Block.DefaultSize + 10000); var customHeader = new DemoCustomHeader(new BlockStream.BlockPosition()); - var header = new CacheHeader(CacheConstants.Identifier, CacheConstants.SchemaVersion, + var header = new DemoHeader(CacheConstants.Identifier, CacheConstants.SchemaVersion, CacheConstants.DataVersion, Source.Ensembl, NumTicks, ExpectedGenomeAssembly, customHeader); using (var ms = new MemoryStream()) @@ -61,22 +62,22 @@ public static byte[] GetRandomBytes(int numBytes) // ReSharper disable once UnusedParameter.Local private static void ReadFromBlockStream(ICompressionAlgorithm compressionAlgorithm, MemoryStream ms, string expectedRandomString) { + // grab the header + var header = DemoHeader.Read(ms); + Assert.Equal(ExpectedGenomeAssembly, header.GenomeAssembly); + using (var blockStream = new BlockStream(compressionAlgorithm, ms, CompressionMode.Decompress)) using (var reader = new ExtendedBinaryReader(blockStream)) { CheckWriteException(blockStream); - // grab the header - var header = GetHeader(blockStream, out var customHeader); - Assert.Equal(ExpectedGenomeAssembly, header.GenomeAssembly); - // sequential string check CheckString(reader, expectedRandomString); CheckString(reader, SmallString); CheckString(reader, FinalString); // random access string check - blockStream.SetBlockPosition(customHeader.DemoPosition); + blockStream.SetBlockPosition(header.Custom.DemoPosition); CheckString(reader, SmallString); } } @@ -90,14 +91,7 @@ private static void CheckString(IExtendedBinaryReader reader, string expectedStr Assert.Equal(expectedString, s); } - private static CacheHeader GetHeader(BlockStream blockStream, out DemoCustomHeader customHeader) - { - var header = (CacheHeader)blockStream.ReadHeader(CacheHeader.Read, DemoCustomHeader.Read); - customHeader = header.CustomHeader as DemoCustomHeader; - return header; - } - - private static void WriteBlockStream(ICompressionAlgorithm compressionAlgorithm, CacheHeader header, + private static void WriteBlockStream(ICompressionAlgorithm compressionAlgorithm, DemoHeader header, DemoCustomHeader customHeader, MemoryStream ms, string s) { using (var blockStream = new BlockStream(compressionAlgorithm, ms, CompressionMode.Compress, true)) @@ -290,7 +284,41 @@ public void DoubleDispose() } } - public sealed class DemoCustomHeader : ICustomCacheHeader + public sealed class DemoHeader : Header + { + public readonly DemoCustomHeader Custom; + + public DemoHeader(string identifier, ushort schemaVersion, ushort dataVersion, Source source, + long creationTimeTicks, GenomeAssembly genomeAssembly, DemoCustomHeader customHeader) : base( + identifier, schemaVersion, dataVersion, source, creationTimeTicks, genomeAssembly) + { + Custom = customHeader; + } + + public new void Write(BinaryWriter writer) + { + base.Write(writer); + Custom.Write(writer); + } + + public static DemoHeader Read(Stream stream) + { + DemoHeader header; + + using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) + { + var baseHeader = Read(reader); + var customHeader = DemoCustomHeader.Read(reader); + + header = new DemoHeader(baseHeader.Identifier, baseHeader.SchemaVersion, baseHeader.DataVersion, + baseHeader.Source, baseHeader.CreationTimeTicks, baseHeader.GenomeAssembly, customHeader); + } + + return header; + } + } + + public sealed class DemoCustomHeader { public readonly BlockStream.BlockPosition DemoPosition; @@ -305,10 +333,10 @@ public void Write(BinaryWriter writer) writer.Write(DemoPosition.InternalOffset); } - public static ICustomCacheHeader Read(BinaryReader reader) + public static DemoCustomHeader Read(BinaryReader reader) { - var fileOffset = reader.ReadInt64(); - var internalOffset = reader.ReadInt32(); + long fileOffset = reader.ReadInt64(); + int internalOffset = reader.ReadInt32(); var bp = new BlockStream.BlockPosition { diff --git a/UnitTests/OptimizedCore/StringExtensionsTests.cs b/UnitTests/OptimizedCore/StringExtensionsTests.cs new file mode 100644 index 00000000..6d3e5892 --- /dev/null +++ b/UnitTests/OptimizedCore/StringExtensionsTests.cs @@ -0,0 +1,71 @@ +using OptimizedCore; +using Xunit; + +namespace UnitTests.OptimizedCore +{ + public sealed class StringExtensionsTests + { + [Theory] + [InlineData("\tjane\tjim")] + [InlineData("bob\tjane\t")] + [InlineData("bob\tjane\tjim")] + public void OptimizedSplit(string s) + { + var observedResult = s.OptimizedSplit('\t'); + var expectedResult = s.Split('\t'); + Assert.Equal(expectedResult, observedResult); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("0")] + [InlineData("123")] + [InlineData("-123")] + [InlineData("2147483647")] + [InlineData("-2147483647")] + [InlineData("4444444444")] + [InlineData("123.3")] + public void OptimizedParseInt32(string s) + { + var observedResult = s.OptimizedParseInt32(); + bool expectedFoundError = !int.TryParse(s, out int expectedResult); + + Assert.Equal(expectedFoundError, observedResult.FoundError); + Assert.Equal(expectedResult, observedResult.Number); + } + + [Theory] + [InlineData("#CHROM", '#')] + [InlineData("#CHROM", 'L')] + public void OptimizedStartsWith(string s, char leadingChar) + { + bool observedResult = s.OptimizedStartsWith(leadingChar); + bool expectedResult = s.StartsWith(leadingChar); + Assert.Equal(expectedResult, observedResult); + } + + [Theory] + [InlineData("END=123")] + [InlineData("RECOMPOSED")] + public void OptimizedKeyValue(string s) + { + var observedResult = s.OptimizedKeyValue(); + var expectedResult = s.Split('='); + + Assert.Equal(expectedResult[0], observedResult.Key); + if (expectedResult.Length == 1) Assert.Null(observedResult.Value); + else Assert.Equal(expectedResult[1], observedResult.Value); + } + + [Theory] + [InlineData("", '>')] + [InlineData("", 'L')] + public void OptimizedEndsWith(string s, char leadingChar) + { + bool observedResult = s.OptimizedEndsWith(leadingChar); + bool expectedResult = s.EndsWith(leadingChar); + Assert.Equal(expectedResult, observedResult); + } + } +} diff --git a/UnitTests/VariantAnnotation/IO/Caches/CacheHeaderTests.cs b/UnitTests/VariantAnnotation/IO/Caches/CacheHeaderTests.cs index c034b33d..3c68e779 100644 --- a/UnitTests/VariantAnnotation/IO/Caches/CacheHeaderTests.cs +++ b/UnitTests/VariantAnnotation/IO/Caches/CacheHeaderTests.cs @@ -2,7 +2,6 @@ using System.Text; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.Sequence; -using VariantAnnotation.IO; using VariantAnnotation.IO.Caches; using Xunit; @@ -18,9 +17,9 @@ public void CacheHeader_EndToEnd() const GenomeAssembly expectedGenomeAssembly = GenomeAssembly.hg19; const ushort expectedVepVersion = ushort.MaxValue; + var expectedBaseHeader = new Header("VEP", 1, 2, expectedTranscriptSource, expectedCreationTimeTicks, expectedGenomeAssembly); var expectedCustomHeader = new TranscriptCacheCustomHeader(expectedVepVersion, 0); - var expectedHeader = new CacheHeader("VEP", 1, 2, expectedTranscriptSource, expectedCreationTimeTicks, - expectedGenomeAssembly, expectedCustomHeader); + var expectedHeader = new CacheHeader(expectedBaseHeader, expectedCustomHeader); CacheHeader observedHeader; @@ -32,21 +31,14 @@ public void CacheHeader_EndToEnd() } ms.Position = 0; - - using (var reader = new ExtendedBinaryReader(ms)) - { - observedHeader = CacheHeader.Read(reader, TranscriptCacheCustomHeader.Read) as CacheHeader; - } + observedHeader = CacheHeader.Read(ms); } Assert.NotNull(observedHeader); - Assert.Equal(expectedTranscriptSource, observedHeader.TranscriptSource); + Assert.Equal(expectedTranscriptSource, observedHeader.Source); Assert.Equal(expectedCreationTimeTicks, observedHeader.CreationTimeTicks); - Assert.Equal(expectedGenomeAssembly, observedHeader.GenomeAssembly); - - var observedCustomHeader = observedHeader.CustomHeader as TranscriptCacheCustomHeader; - Assert.NotNull(observedCustomHeader); - Assert.Equal(expectedVepVersion, observedCustomHeader.VepVersion); + Assert.Equal(expectedGenomeAssembly, observedHeader.GenomeAssembly); + Assert.Equal(expectedVepVersion, observedHeader.Custom.VepVersion); } } } diff --git a/UnitTests/VariantAnnotation/IO/Caches/TranscriptCacheReaderTests.cs b/UnitTests/VariantAnnotation/IO/Caches/TranscriptCacheReaderTests.cs index a731e7e0..20e4b2f3 100644 --- a/UnitTests/VariantAnnotation/IO/Caches/TranscriptCacheReaderTests.cs +++ b/UnitTests/VariantAnnotation/IO/Caches/TranscriptCacheReaderTests.cs @@ -38,8 +38,9 @@ public TranscriptCacheReaderTests() const GenomeAssembly genomeAssembly = GenomeAssembly.GRCh38; + var baseHeader = new Header("test", 2, 3, Source.BothRefSeqAndEnsembl, 4, genomeAssembly); var customHeader = new TranscriptCacheCustomHeader(1, 2); - _expectedHeader = new CacheHeader("test", 2, 3, Source.BothRefSeqAndEnsembl, 4, genomeAssembly, customHeader); + _expectedHeader = new CacheHeader(baseHeader, customHeader); var transcriptRegions = new ITranscriptRegion[] { @@ -104,7 +105,7 @@ private static void CheckIntervalArrays(IntervalArray[] expected, Interval { Assert.Equal(expected.Length, observed.Length); - for (int refIndex = 0; refIndex < expected.Length; refIndex++) + for (var refIndex = 0; refIndex < expected.Length; refIndex++) { var expectedIntervalArray = expected[refIndex]; var observedIntervalArray = observed[refIndex]; @@ -115,7 +116,7 @@ private static void CheckIntervalArrays(IntervalArray[] expected, Interval Assert.NotNull(observedIntervalArray); Assert.Equal(expectedIntervalArray.Array.Length, observedIntervalArray.Array.Length); - for (int i = 0; i < expectedIntervalArray.Array.Length; i++) + for (var i = 0; i < expectedIntervalArray.Array.Length; i++) { var expectedInterval = expectedIntervalArray.Array[i]; var observedInterval = observedIntervalArray.Array[i]; @@ -133,7 +134,7 @@ private static void CheckChromosomeIntervals(IEnumerable ex Assert.Equal(expectedList.Count, observedList.Count); - for (int i = 0; i < expectedList.Count; i++) + for (var i = 0; i < expectedList.Count; i++) { var expectedEntry = expectedList[i]; var observedEntry = observedList[i]; @@ -150,7 +151,7 @@ private static void CheckIntervals(IEnumerable expected, IEnumerable< Assert.Equal(expectedList.Count, observedList.Count); - for (int i = 0; i < expectedList.Count; i++) + for (var i = 0; i < expectedList.Count; i++) { var expectedEntry = expectedList[i]; var observedEntry = observedList[i]; diff --git a/UnitTests/Vcf/StringExtensionsTests.cs b/UnitTests/Vcf/StringExtensionsTests.cs index 2086bab5..4f915f30 100644 --- a/UnitTests/Vcf/StringExtensionsTests.cs +++ b/UnitTests/Vcf/StringExtensionsTests.cs @@ -32,21 +32,21 @@ public void GetNullableValue_double(string input, double? exp) [InlineData("12,13.0", null)] public void SplitToArray_int(string input, int[] exp) { - var observe = input.SplitToArray(',',int.TryParse); + var observe = input.SplitToArray(); Assert.Equal(exp, observe); } - [Theory] - [InlineData("12", new double[] { 12 })] - [InlineData("12,13", new double[] { 12, 13 })] - [InlineData("12,13.0", new[] { 12, 13.0})] - [InlineData("12.a,13.0", null)] - public void SplitToArray_double(string input, double[] exp) - { - var observe = input.SplitToArray(',', double.TryParse); - Assert.Equal(exp, observe); - } + //[Theory] + //[InlineData("12", new double[] { 12 })] + //[InlineData("12,13", new double[] { 12, 13 })] + //[InlineData("12,13.0", new[] { 12, 13.0})] + //[InlineData("12.a,13.0", null)] + //public void SplitToArray_double(string input, double[] exp) + //{ + // var observe = input.SplitToArray(',', double.TryParse); + // Assert.Equal(exp, observe); + //} } } \ No newline at end of file diff --git a/UnitTests/Vcf/VariantCreator/VariantFactoryTests.cs b/UnitTests/Vcf/VariantCreator/VariantFactoryTests.cs index 95ff0354..c66e7ba0 100644 --- a/UnitTests/Vcf/VariantCreator/VariantFactoryTests.cs +++ b/UnitTests/Vcf/VariantCreator/VariantFactoryTests.cs @@ -19,7 +19,7 @@ public void GetVariant_svDel() var chromosome1 = new Chromosome("chr1", "1", 0); var variantFactory = new VariantFactory(new Dictionary { { "1", chromosome1 } }, null,false); - var variants = variantFactory.CreateVariants(chromosome1,null, 69391, 138730, "A", new[] { "" }, infoData, new[] { false }, false); + var variants = variantFactory.CreateVariants(chromosome1, 69391, 138730, "A", new[] { "" }, infoData, new[] { false }, false); Assert.NotNull(variants); Assert.Equal(2, variants[0].BreakEnds.Length); } @@ -32,7 +32,7 @@ public void GetVariant_canvas_cnv() var chromosome1 = new Chromosome("chr1", "1", 0); var variantFactory = new VariantFactory(new Dictionary { { "1", chromosome1 } }, null, false); - var variants = variantFactory.CreateVariants(chromosome1, "Canvas:GAIN:1:723708:2581225", 723707, 2581225, "N", new[] { "" }, infoData, new[] { false }, false); + var variants = variantFactory.CreateVariants(chromosome1, 723707, 2581225, "N", new[] { "" }, infoData, new[] { false }, false); Assert.NotNull(variants); Assert.Null(variants[0].BreakEnds); @@ -48,7 +48,7 @@ public void GetVariant_canvas_cnx() var chromosome1 = new Chromosome("chr1", "1", 0); var variantFactory = new VariantFactory(new Dictionary { { "1", chromosome1 } }, null, false); - var variants = variantFactory.CreateVariants(chromosome1, "Canvas:COMPLEXCNV:chr1:854896-861879", 854895, 861879, "N", new[] { "", "" }, infoData, new[] { false, false }, false); + var variants = variantFactory.CreateVariants(chromosome1, 854895, 861879, "N", new[] { "", "" }, infoData, new[] { false, false }, false); Assert.NotNull(variants); Assert.Equal(2, variants.Length); @@ -67,7 +67,7 @@ public void GetVariant_canvas_cnv_dup() var chromosome1 = new Chromosome("chr1", "1", 0); var variantFactory = new VariantFactory(new Dictionary { { "1", chromosome1 } }, null, false); - var variants = variantFactory.CreateVariants(chromosome1, "Canvas:COMPLEXCNV:chr1:1463186-1476229", 1463185, 1476229, "N", new[] { "", "" }, infoData, new[] {false, false}, false); + var variants = variantFactory.CreateVariants(chromosome1, 1463185, 1476229, "N", new[] { "", "" }, infoData, new[] {false, false}, false); Assert.NotNull(variants); Assert.Equal(2, variants.Length); @@ -86,7 +86,7 @@ public void GetVariant_dup() var chromosome1 = new Chromosome("chr1", "1", 0); var variantFactory = new VariantFactory(new Dictionary { { "1", chromosome1 } }, null, false); - var variants = variantFactory.CreateVariants(chromosome1, null, 1463185, 1476229, "N", new[] { "" }, infoData, new[] { false}, false); + var variants = variantFactory.CreateVariants(chromosome1, 1463185, 1476229, "N", new[] { "" }, infoData, new[] { false}, false); Assert.NotNull(variants); Assert.Single(variants); @@ -103,7 +103,7 @@ public void GetVariant_tandem_duplication() var chromosome1 = new Chromosome("chr1", "1", 0); var variantFactory = new VariantFactory(new Dictionary { { "1", chromosome1 } }, null, false); - var variants = variantFactory.CreateVariants(chromosome1, null, 723707, 2581225, "N", new[] { "" }, infoData, new[] { false }, false); + var variants = variantFactory.CreateVariants(chromosome1, 723707, 2581225, "N", new[] { "" }, infoData, new[] { false }, false); Assert.NotNull(variants); Assert.Equal(VariantType.tandem_duplication, variants[0].Type); diff --git a/UnitTests/Vcf/VcfReaderTests.cs b/UnitTests/Vcf/VcfReaderTests.cs index de6cb38c..1e11ab6e 100644 --- a/UnitTests/Vcf/VcfReaderTests.cs +++ b/UnitTests/Vcf/VcfReaderTests.cs @@ -3,16 +3,11 @@ using System.IO; using System.Linq; using Moq; -using Phantom.Workers; -using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Positions; using VariantAnnotation.Interface.Providers; using VariantAnnotation.Interface.Sequence; using VariantAnnotation.Sequence; using Vcf; -using Vcf.Info; -using Vcf.Sample; -using Vcf.VariantCreator; using Xunit; namespace UnitTests.Vcf diff --git a/VariantAnnotation.Interface/IO/IFileHeader.cs b/VariantAnnotation.Interface/IO/IFileHeader.cs deleted file mode 100644 index 343e3a50..00000000 --- a/VariantAnnotation.Interface/IO/IFileHeader.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.IO; - -namespace VariantAnnotation.Interface.IO -{ - public interface IFileHeader - { - void Write(BinaryWriter writer); - } - - public interface ICustomCacheHeader : IFileHeader { } -} diff --git a/VariantAnnotation.Interface/Intervals/NullableInterval.cs b/VariantAnnotation.Interface/Intervals/NullableInterval.cs deleted file mode 100644 index 45514aee..00000000 --- a/VariantAnnotation.Interface/Intervals/NullableInterval.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace VariantAnnotation.Interface.Intervals -{ - public struct NullableInterval - { - public readonly int? Start; - public readonly int? End; - - public NullableInterval(int? start, int? end) - { - Start = start; - End = end; - } - } -} \ No newline at end of file diff --git a/VariantAnnotation.Interface/Phantom/IRecomposer.cs b/VariantAnnotation.Interface/Phantom/IRecomposer.cs index 64c44086..497345ed 100644 --- a/VariantAnnotation.Interface/Phantom/IRecomposer.cs +++ b/VariantAnnotation.Interface/Phantom/IRecomposer.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.IO; using VariantAnnotation.Interface.Positions; namespace VariantAnnotation.Interface.Phantom diff --git a/VariantAnnotation.Interface/Positions/IPosition.cs b/VariantAnnotation.Interface/Positions/IPosition.cs index f110e7f7..ac6d72e6 100644 --- a/VariantAnnotation.Interface/Positions/IPosition.cs +++ b/VariantAnnotation.Interface/Positions/IPosition.cs @@ -1,6 +1,4 @@ -using VariantAnnotation.Interface.Intervals; - -namespace VariantAnnotation.Interface.Positions +namespace VariantAnnotation.Interface.Positions { public interface IPosition : ISimplePosition { diff --git a/VariantAnnotation/AnnotatedPositions/AnnotatedSaDataSource.cs b/VariantAnnotation/AnnotatedPositions/AnnotatedSaDataSource.cs index 32efa2c4..52e8f85c 100644 --- a/VariantAnnotation/AnnotatedPositions/AnnotatedSaDataSource.cs +++ b/VariantAnnotation/AnnotatedPositions/AnnotatedSaDataSource.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using CommonUtilities; +using OptimizedCore; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.SA; using VariantAnnotation.IO; @@ -39,7 +40,7 @@ public IList GetJsonStrings() public IEnumerable GetVcfStrings() { - return SaDataSource.VcfString.Split(','); + return SaDataSource.VcfString.OptimizedSplit(','); } } } \ No newline at end of file diff --git a/VariantAnnotation/AnnotatedPositions/HgvsProteinNomenclature.cs b/VariantAnnotation/AnnotatedPositions/HgvsProteinNomenclature.cs index 9da7ab17..83f6cff0 100644 --- a/VariantAnnotation/AnnotatedPositions/HgvsProteinNomenclature.cs +++ b/VariantAnnotation/AnnotatedPositions/HgvsProteinNomenclature.cs @@ -1,4 +1,5 @@ -using VariantAnnotation.Algorithms; +using OptimizedCore; +using VariantAnnotation.Algorithms; using VariantAnnotation.AnnotatedPositions.Transcript; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.Positions; @@ -25,10 +26,10 @@ public static string GetHgvsProteinAnnotation( var peptideSeq = transcript.Translation.PeptideSeq; - // Amino acid seq should never go past the stop codon - refAminoAcids = !refAminoAcids.EndsWith(AminoAcids.StopCodon) && refAminoAcids.Contains(AminoAcids.StopCodon) - ? refAminoAcids.Split(AminoAcids.StopCodon[0])[0]+AminoAcids.StopCodon - : refAminoAcids; + // Amino acid seq should never go past the stop codon + refAminoAcids = !refAminoAcids.EndsWith(AminoAcids.StopCodon) && refAminoAcids.Contains(AminoAcids.StopCodon) + ? refAminoAcids.OptimizedSplit(AminoAcids.StopCodon[0])[0] + AminoAcids.StopCodon + : refAminoAcids; int proteinStart = position.ProteinStart; HgvsUtilities.ShiftAndRotateAlleles(ref proteinStart, ref refAminoAcids, ref altAminoAcids, peptideSeq); @@ -135,7 +136,7 @@ internal static ProteinChange GetProteinChange(int start, string refAminoAcids, // todo: add start gained // according to var nom, only if the Stop codon is effected, we call it an extension - if (variantEffect.IsStopLost() && refAminoAcids.StartsWith(AminoAcids.StopCodon)) return ProteinChange.Extension; + if (variantEffect.IsStopLost() && refAminoAcids.OptimizedStartsWith(AminoAcids.StopCodonChar)) return ProteinChange.Extension; if (variantEffect.IsFrameshiftVariant()) return ProteinChange.Frameshift; diff --git a/VariantAnnotation/AnnotatedPositions/Transcript/VariantEffect.cs b/VariantAnnotation/AnnotatedPositions/Transcript/VariantEffect.cs index 99a3960e..704e6971 100644 --- a/VariantAnnotation/AnnotatedPositions/Transcript/VariantEffect.cs +++ b/VariantAnnotation/AnnotatedPositions/Transcript/VariantEffect.cs @@ -1,4 +1,5 @@ using System; +using OptimizedCore; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.Positions; @@ -245,7 +246,7 @@ private bool IsTruncatedByStop() { var stopPos = _alternateAminoAcids.IndexOf(AminoAcids.StopCodon, StringComparison.Ordinal); var altAminoAcidesBeforeStop = _alternateAminoAcids.Substring(0, stopPos); - if (_alternateAminoAcids.StartsWith(AminoAcids.StopCodon) || + if (_alternateAminoAcids.OptimizedStartsWith(AminoAcids.StopCodonChar) || _referenceAminoAcids.StartsWith(altAminoAcidesBeforeStop)) return true; } @@ -358,7 +359,7 @@ public bool IsProteinAlteringVariant() var result = true; var sameLen = _referenceAminoAcidsLen == _alternateAminoAcidsLen; - var startsWithTer = _referenceAminoAcids.StartsWith("X") || _alternateAminoAcids.StartsWith("X"); + var startsWithTer = _referenceAminoAcids.OptimizedStartsWith('X') || _alternateAminoAcids.OptimizedStartsWith('X'); var isInframeDeletion = IsInframeDeletion(); // Note: sequence ontology says that stop retained should not be here (http://www.sequenceontology.org/browser/current_svn/term/SO:0001567) diff --git a/VariantAnnotation/Caches/TranscriptCacheData.cs b/VariantAnnotation/Caches/TranscriptCacheData.cs index c0932080..d1469eef 100644 --- a/VariantAnnotation/Caches/TranscriptCacheData.cs +++ b/VariantAnnotation/Caches/TranscriptCacheData.cs @@ -43,10 +43,9 @@ private static IEnumerable GetDataSourceVersions(CacheHeader var dataSourceVersions = new List(); if (header == null) return dataSourceVersions; - var customHeader = header.CustomHeader as TranscriptCacheCustomHeader; - var vepVersion = customHeader?.VepVersion; + ushort vepVersion = header.Custom.VepVersion; - var dataSourceVersion = new DataSourceVersion("VEP", vepVersion.ToString(), header.CreationTimeTicks, header.TranscriptSource.ToString()); + var dataSourceVersion = new DataSourceVersion("VEP", vepVersion.ToString(), header.CreationTimeTicks, header.Source.ToString()); dataSourceVersions.Add(dataSourceVersion); return dataSourceVersions; } diff --git a/VariantAnnotation/CommonAssemblyInfo.props b/VariantAnnotation/CommonAssemblyInfo.props index 11cefec3..4ebc0ab5 100644 --- a/VariantAnnotation/CommonAssemblyInfo.props +++ b/VariantAnnotation/CommonAssemblyInfo.props @@ -2,9 +2,9 @@ Illumina © 2018 Illumina, Inc. - 2.0.8.0 - 2.0.8.0 - 2.0.8 + 2.0.9.0 + 2.0.9.0 + 2.0.9 Stromberg, Roy, Lajugie, Jiang, Li, and Kang \ No newline at end of file diff --git a/VariantAnnotation/IO/Caches/CacheHeader.cs b/VariantAnnotation/IO/Caches/CacheHeader.cs index 919767ee..3ebfd814 100644 --- a/VariantAnnotation/IO/Caches/CacheHeader.cs +++ b/VariantAnnotation/IO/Caches/CacheHeader.cs @@ -1,56 +1,37 @@ -using System; -using System.IO; -using VariantAnnotation.Interface.AnnotatedPositions; -using VariantAnnotation.Interface.IO; -using VariantAnnotation.Interface.Sequence; +using System.IO; +using System.Text; namespace VariantAnnotation.IO.Caches { - public sealed class CacheHeader : IFileHeader + public sealed class CacheHeader : Header { - private readonly string _identifier; - public readonly ushort SchemaVersion; - public readonly ushort DataVersion; - public readonly Source TranscriptSource; - public readonly long CreationTimeTicks; - public readonly GenomeAssembly GenomeAssembly; - public readonly ICustomCacheHeader CustomHeader; + public readonly TranscriptCacheCustomHeader Custom; - public CacheHeader(string identifier, ushort schemaVersion, ushort dataVersion, Source transcriptSource, - long creationTimeTicks, GenomeAssembly genomeAssembly, ICustomCacheHeader customHeader) + public CacheHeader(Header header, TranscriptCacheCustomHeader customHeader) : base(header.Identifier, + header.SchemaVersion, header.DataVersion, header.Source, header.CreationTimeTicks, + header.GenomeAssembly) { - _identifier = identifier; - SchemaVersion = schemaVersion; - DataVersion = dataVersion; - TranscriptSource = transcriptSource; - CreationTimeTicks = creationTimeTicks; - GenomeAssembly = genomeAssembly; - CustomHeader = customHeader; + Custom = customHeader; } - public void Write(BinaryWriter writer) + public new void Write(BinaryWriter writer) { - writer.Write(_identifier); - writer.Write(SchemaVersion); - writer.Write(DataVersion); - writer.Write((byte)TranscriptSource); - writer.Write(CreationTimeTicks); - writer.Write((byte)GenomeAssembly); - CustomHeader.Write(writer); + base.Write(writer); + Custom.Write(writer); } - public static IFileHeader Read(BinaryReader reader, Func customRead) + public static CacheHeader Read(Stream stream) { - var identifier = reader.ReadString(); - var schemaVersion = reader.ReadUInt16(); - var dataVersion = reader.ReadUInt16(); - var transcriptSource = (Source)reader.ReadByte(); - var creationTimeTicks = reader.ReadInt64(); - var genomeAssembly = (GenomeAssembly)reader.ReadByte(); - var customHeader = customRead(reader); + CacheHeader header; - return new CacheHeader(identifier, schemaVersion, dataVersion, transcriptSource, - creationTimeTicks, genomeAssembly, customHeader); + using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) + { + var baseHeader = Read(reader); + var customHeader = TranscriptCacheCustomHeader.Read(reader); + header = new CacheHeader(baseHeader, customHeader); + } + + return header; } } } diff --git a/VariantAnnotation/IO/Caches/Header.cs b/VariantAnnotation/IO/Caches/Header.cs new file mode 100644 index 00000000..24a6a55c --- /dev/null +++ b/VariantAnnotation/IO/Caches/Header.cs @@ -0,0 +1,49 @@ +using System.IO; +using VariantAnnotation.Interface.AnnotatedPositions; +using VariantAnnotation.Interface.Sequence; + +namespace VariantAnnotation.IO.Caches +{ + public class Header + { + public readonly string Identifier; + public readonly ushort SchemaVersion; + public readonly ushort DataVersion; + public readonly Source Source; + public readonly long CreationTimeTicks; + public readonly GenomeAssembly GenomeAssembly; + + public Header(string identifier, ushort schemaVersion, ushort dataVersion, Source source, + long creationTimeTicks, GenomeAssembly genomeAssembly) + { + Identifier = identifier; + SchemaVersion = schemaVersion; + DataVersion = dataVersion; + Source = source; + CreationTimeTicks = creationTimeTicks; + GenomeAssembly = genomeAssembly; + } + + protected void Write(BinaryWriter writer) + { + writer.Write(Identifier); + writer.Write(SchemaVersion); + writer.Write(DataVersion); + writer.Write((byte)Source); + writer.Write(CreationTimeTicks); + writer.Write((byte)GenomeAssembly); + } + + protected static Header Read(BinaryReader reader) + { + string identifier = reader.ReadString(); + ushort schemaVersion = reader.ReadUInt16(); + ushort dataVersion = reader.ReadUInt16(); + var source = (Source)reader.ReadByte(); + long creationTimeTicks = reader.ReadInt64(); + var genomeAssembly = (GenomeAssembly)reader.ReadByte(); + + return new Header(identifier, schemaVersion, dataVersion, source, creationTimeTicks, genomeAssembly); + } + } +} diff --git a/VariantAnnotation/IO/Caches/PredictionCacheCustomHeader.cs b/VariantAnnotation/IO/Caches/PredictionCacheCustomHeader.cs index 85039185..a5fd6cc2 100644 --- a/VariantAnnotation/IO/Caches/PredictionCacheCustomHeader.cs +++ b/VariantAnnotation/IO/Caches/PredictionCacheCustomHeader.cs @@ -1,17 +1,13 @@ using System.IO; using VariantAnnotation.Caches.DataStructures; -using VariantAnnotation.Interface.IO; namespace VariantAnnotation.IO.Caches { - public sealed class PredictionCacheCustomHeader : ICustomCacheHeader + public sealed class PredictionCacheCustomHeader { public readonly IndexEntry[] Entries; - public PredictionCacheCustomHeader(IndexEntry[] entries) - { - Entries = entries; - } + public PredictionCacheCustomHeader(IndexEntry[] entries) => Entries = entries; public void Write(BinaryWriter writer) { @@ -19,11 +15,11 @@ public void Write(BinaryWriter writer) foreach (var entry in Entries) entry.Write(writer); } - public static ICustomCacheHeader Read(BinaryReader reader) + public static PredictionCacheCustomHeader Read(BinaryReader reader) { - var numReferenceSeqs = reader.ReadUInt16(); - var entries = new IndexEntry[numReferenceSeqs]; - for (int i = 0; i < numReferenceSeqs; i++) entries[i].Read(reader); + ushort numReferenceSeqs = reader.ReadUInt16(); + var entries = new IndexEntry[numReferenceSeqs]; + for (var i = 0; i < numReferenceSeqs; i++) entries[i].Read(reader); return new PredictionCacheCustomHeader(entries); } } diff --git a/VariantAnnotation/IO/Caches/PredictionCacheReader.cs b/VariantAnnotation/IO/Caches/PredictionCacheReader.cs index 3e998d25..790c8803 100644 --- a/VariantAnnotation/IO/Caches/PredictionCacheReader.cs +++ b/VariantAnnotation/IO/Caches/PredictionCacheReader.cs @@ -18,27 +18,15 @@ public sealed class PredictionCacheReader : IDisposable private readonly IndexEntry[] _indexEntries; public readonly PredictionHeader Header; - public PredictionCacheReader(Stream fs, string[] predictionDescriptions) + public PredictionCacheReader(Stream stream, string[] predictionDescriptions) { - _blockStream = new BlockStream(new Zstandard(), fs, CompressionMode.Decompress); - _reader = new BinaryReader(_blockStream, Encoding.UTF8, true); - _predictionDescriptions = predictionDescriptions; - - Header = GetHeader(); - _indexEntries = GetIndexEntries(Header.Header); - } + _blockStream = new BlockStream(new Zstandard(), stream, CompressionMode.Decompress); + Header = PredictionHeader.Read(stream, _blockStream); - private PredictionHeader GetHeader() - { - var header = _blockStream.ReadHeader(CacheHeader.Read, PredictionCacheCustomHeader.Read) as CacheHeader; - var lut = ReadLookupTable(_reader); - return new PredictionHeader(header, lut); - } + _reader = new BinaryReader(_blockStream, Encoding.UTF8, true); + _predictionDescriptions = predictionDescriptions; - private static IndexEntry[] GetIndexEntries(CacheHeader header) - { - var customHeader = header.CustomHeader as PredictionCacheCustomHeader; - return customHeader?.Entries; + _indexEntries = Header.Custom.Entries; } public void Dispose() @@ -47,21 +35,13 @@ public void Dispose() _blockStream.Dispose(); } - private static Prediction.Entry[] ReadLookupTable(BinaryReader reader) - { - var numEntries = reader.ReadInt32(); - var lut = new Prediction.Entry[numEntries]; - for (int i = 0; i < numEntries; i++) lut[i] = Prediction.Entry.Read(reader); - return lut; - } - /// /// parses the database cache file and populates the specified lists and interval trees /// public IPredictionCache Read(ushort refIndex) { var predictions = GetPredictions(refIndex); - return new PredictionCache(Header.Header.GenomeAssembly, predictions, _predictionDescriptions); + return new PredictionCache(Header.GenomeAssembly, predictions, _predictionDescriptions); } public Prediction[] GetPredictions(ushort refIndex) @@ -72,7 +52,7 @@ public Prediction[] GetPredictions(ushort refIndex) _blockStream.SetBlockPosition(bp); var predictions = new Prediction[indexEntry.Count]; - for (int i = 0; i < indexEntry.Count; i++) predictions[i] = Prediction.Read(_reader, Header.Lut); + for (var i = 0; i < indexEntry.Count; i++) predictions[i] = Prediction.Read(_reader, Header.LookupTable); return predictions; } @@ -87,17 +67,5 @@ public Prediction[] GetPredictions(ushort refIndex) { "probably damaging", "possibly damaging", "benign", "unknown" }; - - public sealed class PredictionHeader - { - public readonly CacheHeader Header; - public readonly Prediction.Entry[] Lut; - - public PredictionHeader(CacheHeader header, Prediction.Entry[] lut) - { - Header = header; - Lut = lut; - } - } } } \ No newline at end of file diff --git a/VariantAnnotation/IO/Caches/PredictionHeader.cs b/VariantAnnotation/IO/Caches/PredictionHeader.cs new file mode 100644 index 00000000..c5cea1cd --- /dev/null +++ b/VariantAnnotation/IO/Caches/PredictionHeader.cs @@ -0,0 +1,55 @@ +using System.IO; +using System.Text; +using Compression.FileHandling; +using VariantAnnotation.Caches.DataStructures; + +namespace VariantAnnotation.IO.Caches +{ + public sealed class PredictionHeader : Header + { + public readonly PredictionCacheCustomHeader Custom; + public readonly Prediction.Entry[] LookupTable; + + public PredictionHeader(Header header, PredictionCacheCustomHeader customHeader, Prediction.Entry[] lookupTable) + : base(header.Identifier, header.SchemaVersion, header.DataVersion, header.Source, + header.CreationTimeTicks, header.GenomeAssembly) + { + Custom = customHeader; + LookupTable = lookupTable; + } + + public new void Write(BinaryWriter writer) + { + base.Write(writer); + Custom.Write(writer); + } + + public static PredictionHeader Read(Stream stream, BlockStream blockStream) + { + Header baseHeader; + PredictionCacheCustomHeader customHeader; + Prediction.Entry[] lookupTable; + + using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) + { + baseHeader = Read(reader); + customHeader = PredictionCacheCustomHeader.Read(reader); + } + + using (var reader = new BinaryReader(blockStream, Encoding.UTF8, true)) + { + lookupTable = ReadLookupTable(reader); + } + + return new PredictionHeader(baseHeader, customHeader, lookupTable); + } + + private static Prediction.Entry[] ReadLookupTable(BinaryReader reader) + { + int numEntries = reader.ReadInt32(); + var lut = new Prediction.Entry[numEntries]; + for (var i = 0; i < numEntries; i++) lut[i] = Prediction.Entry.Read(reader); + return lut; + } + } +} diff --git a/VariantAnnotation/IO/Caches/TranscriptCacheCustomHeader.cs b/VariantAnnotation/IO/Caches/TranscriptCacheCustomHeader.cs index 69fad5e7..aa9468d3 100644 --- a/VariantAnnotation/IO/Caches/TranscriptCacheCustomHeader.cs +++ b/VariantAnnotation/IO/Caches/TranscriptCacheCustomHeader.cs @@ -1,9 +1,8 @@ using System.IO; -using VariantAnnotation.Interface.IO; namespace VariantAnnotation.IO.Caches { - public sealed class TranscriptCacheCustomHeader : ICustomCacheHeader + public sealed class TranscriptCacheCustomHeader { public readonly ushort VepVersion; private readonly long _vepReleaseTicks; @@ -20,10 +19,10 @@ public void Write(BinaryWriter writer) writer.Write(VepVersion); } - public static ICustomCacheHeader Read(BinaryReader reader) + public static TranscriptCacheCustomHeader Read(BinaryReader reader) { - var vepReleaseTicks = reader.ReadInt64(); - var vepVersion = reader.ReadUInt16(); + long vepReleaseTicks = reader.ReadInt64(); + ushort vepVersion = reader.ReadUInt16(); return new TranscriptCacheCustomHeader(vepVersion, vepReleaseTicks); } } diff --git a/VariantAnnotation/IO/Caches/TranscriptCacheReader.cs b/VariantAnnotation/IO/Caches/TranscriptCacheReader.cs index e0c23c09..657db006 100644 --- a/VariantAnnotation/IO/Caches/TranscriptCacheReader.cs +++ b/VariantAnnotation/IO/Caches/TranscriptCacheReader.cs @@ -20,9 +20,9 @@ public sealed class TranscriptCacheReader : IDisposable public TranscriptCacheReader(Stream stream) { + Header = CacheHeader.Read(stream); var blockStream = new BlockStream(new Zstandard(), stream, CompressionMode.Decompress); _reader = new ExtendedBinaryReader(blockStream, Encoding.UTF8); - Header = blockStream.ReadHeader(CacheHeader.Read, TranscriptCacheCustomHeader.Read) as CacheHeader; } public void Dispose() => _reader.Dispose(); diff --git a/VariantAnnotation/IO/VcfWriter/VcfConversion.cs b/VariantAnnotation/IO/VcfWriter/VcfConversion.cs index 4ff12de6..dd2b54e1 100644 --- a/VariantAnnotation/IO/VcfWriter/VcfConversion.cs +++ b/VariantAnnotation/IO/VcfWriter/VcfConversion.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Text; +using OptimizedCore; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.IO; @@ -83,7 +84,7 @@ private static string ExtractDbId(IAnnotatedPosition annotatedPosition) private static IEnumerable GetNonDbsnpIds(string idField) { if (idField == null || idField == ".") return null; - var idList = idField.Split(';').Where(id => !id.StartsWith("rs")).ToList(); + var idList = idField.OptimizedSplit(';').Where(id => !id.StartsWith("rs")).ToList(); return idList.Count == 0 ? null : idList; } @@ -180,7 +181,7 @@ private static void ExtractInfo(IAnnotatedPosition annotatedPosition, VcfField i if (sa.SaDataSource.KeyName == OneKgKeyName) { - var contents = vcfAnnotation.Split(';'); + var contents = vcfAnnotation.OptimizedSplit(';'); string freq = contents[0]; string ancestryAllele = string.IsNullOrEmpty(contents[1]) ? null : contents[1]; diff --git a/VariantAnnotation/PhyloP/PhylopWriter.cs b/VariantAnnotation/PhyloP/PhylopWriter.cs index f357be88..7cf4f1c9 100644 --- a/VariantAnnotation/PhyloP/PhylopWriter.cs +++ b/VariantAnnotation/PhyloP/PhylopWriter.cs @@ -4,6 +4,7 @@ using System.Linq; using Compression.Algorithms; using Compression.Utilities; +using OptimizedCore; using VariantAnnotation.Interface.Sequence; using VariantAnnotation.IO; using VariantAnnotation.IO.Caches; @@ -194,7 +195,7 @@ public void ExtractPhylopScores() private void StartNewInterval(string line) { var words = line.Split(); - var chromName = words[1].Split('=')[1]; + var chromName = words[1].OptimizedKeyValue().Value; // checking if the writer needs to be initiated/re-initiated if (_writer == null) @@ -214,8 +215,8 @@ private void StartNewInterval(string line) WriteInterval(_currentInterval, _writer); } - var start = Convert.ToInt32(words[2].Split('=')[1]); - var step = Convert.ToInt16(words[3].Split('=')[1]); + var start = Convert.ToInt32(words[2].OptimizedKeyValue().Value); + var step = Convert.ToInt16(words[3].OptimizedKeyValue().Value); _currentInterval = new PhylopInterval(start, 0, step); } diff --git a/VariantAnnotation/Providers/RefMinorProvider.cs b/VariantAnnotation/Providers/RefMinorProvider.cs index bce606b5..30895b47 100644 --- a/VariantAnnotation/Providers/RefMinorProvider.cs +++ b/VariantAnnotation/Providers/RefMinorProvider.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using OptimizedCore; using VariantAnnotation.Interface.Providers; using VariantAnnotation.Interface.Sequence; using VariantAnnotation.SA; @@ -18,7 +19,7 @@ public RefMinorProvider(List supplementaryAnnotationDirectories) { foreach (var file in Directory.GetFiles(directory, "*.idx")) { - var chromeName = Path.GetFileNameWithoutExtension(file).Split('.')[0]; + var chromeName = Path.GetFileNameWithoutExtension(file).OptimizedSplit('.')[0]; var refMinorPostions = SaIndex.Read(FileUtilities.GetReadStream(file)).GlobalMajorAlleleForRefMinor; if (refMinorPostions.Length > 0) _positionDict[chromeName] = refMinorPostions.ToDictionary(x => x.Position, x => x.GlobalMajorAllele); diff --git a/VariantAnnotation/Providers/TranscriptAnnotationProvider.cs b/VariantAnnotation/Providers/TranscriptAnnotationProvider.cs index a1679002..57df7806 100644 --- a/VariantAnnotation/Providers/TranscriptAnnotationProvider.cs +++ b/VariantAnnotation/Providers/TranscriptAnnotationProvider.cs @@ -50,20 +50,19 @@ public TranscriptAnnotationProvider(string pathPrefix, ISequenceProvider sequen _polyphenReader = new PredictionCacheReader(FileUtilities.GetReadStream(CacheConstants.PolyPhenPath(pathPrefix)), PredictionCacheReader.PolyphenDescriptions); } - private static (TranscriptCache cache, ushort vepVersion) InitiateCache(Stream stream, IDictionary refIndexToChromosome, GenomeAssembly refGenomeAssembly) + private static (TranscriptCache cache, ushort vepVersion) InitiateCache(Stream stream, + IDictionary refIndexToChromosome, GenomeAssembly refGenomeAssembly) { TranscriptCache cache; ushort vepVersion; using (var reader = new TranscriptCacheReader(stream)) { - var customHeader = reader.Header.CustomHeader as TranscriptCacheCustomHeader; - vepVersion = customHeader?.VepVersion ?? 0; - + vepVersion = reader.Header.Custom.VepVersion; CheckHeaderVersion(reader.Header, refGenomeAssembly); cache = reader.Read(refIndexToChromosome).GetCache(); } - + return (cache, vepVersion); } diff --git a/VariantAnnotation/TranscriptAnnotation/FullTranscriptAnnotator.cs b/VariantAnnotation/TranscriptAnnotation/FullTranscriptAnnotator.cs index fcebb51b..191514e8 100644 --- a/VariantAnnotation/TranscriptAnnotation/FullTranscriptAnnotator.cs +++ b/VariantAnnotation/TranscriptAnnotation/FullTranscriptAnnotator.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using VariantAnnotation.Algorithms; using VariantAnnotation.AnnotatedPositions; using VariantAnnotation.AnnotatedPositions.Consequence; diff --git a/VariantAnnotation/VariantAnnotation.csproj b/VariantAnnotation/VariantAnnotation.csproj index 08ba8b23..ff77e9ff 100644 --- a/VariantAnnotation/VariantAnnotation.csproj +++ b/VariantAnnotation/VariantAnnotation.csproj @@ -9,6 +9,7 @@ + diff --git a/Vcf/BreakEnd.cs b/Vcf/BreakEnd.cs index b52892ba..17735e03 100644 --- a/Vcf/BreakEnd.cs +++ b/Vcf/BreakEnd.cs @@ -11,8 +11,8 @@ public sealed class BreakEnd : IBreakEnd public BreakEnd(IChromosome chromosome, IChromosome chromosome2, int position, int position2, bool isSuffix, bool isSuffix2) { - var orientation1 = isSuffix ? '-' : '+'; - var orientation2 = isSuffix2 ? '+' : '-'; + char orientation1 = isSuffix ? '-' : '+'; + char orientation2 = isSuffix2 ? '+' : '-'; Piece1 = new BreakEndPiece(chromosome, position, isSuffix, orientation1); Piece2 = new BreakEndPiece(chromosome2, position2, isSuffix2, orientation2); } diff --git a/Vcf/Info/VcfInfoParser.cs b/Vcf/Info/VcfInfoParser.cs index 99508b55..89373fed 100644 --- a/Vcf/Info/VcfInfoParser.cs +++ b/Vcf/Info/VcfInfoParser.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using CommonUtilities; +using OptimizedCore; using VariantAnnotation.Interface.Positions; namespace Vcf.Info @@ -24,23 +25,30 @@ public static class VcfInfoParser public static IInfoData Parse(string infoField) { - if (string.IsNullOrEmpty(infoField) ) return null; + if (string.IsNullOrEmpty(infoField)) return null; var infoKeyValue = ExtractInfoFields(infoField, out string updatedInfoField); - int? svLen = null, end = null, copyNumber = null, depth = null, jointSomaticNormalQuality = null; - VariantType svType = VariantType.unknown; - var colocalizedWithCnv = false; - int[] ciPos = null, ciEnd = null; - double? strandBias = null, recalibratedQuality = null; - bool isInv3=false, isInv5=false; - int? refRepeatCount=null; - string repeatUnit=null; - - foreach (var kvp in infoKeyValue) + int? svLen = null; + int? end = null; + int? copyNumber = null; + int? depth = null; + int? jointSomaticNormalQuality = null; + VariantType svType = VariantType.unknown; + var colocalizedWithCnv = false; + int[] ciPos = null; + int[] ciEnd = null; + double? strandBias = null; + double? recalibratedQuality = null; + var isInv3 = false; + var isInv5 = false; + int? refRepeatCount = null; + string repeatUnit = null; + + foreach (var kvp in infoKeyValue) { - var key = kvp.Key; - var value = kvp.Value; + string key = kvp.Key; + string value = kvp.Value; // ReSharper disable once SwitchStatementMissingSomeCases switch (key) @@ -51,92 +59,92 @@ public static IInfoData Parse(string infoField) case "QSI_NT": case "SOMATICSCORE": case "QSS_NT": - jointSomaticNormalQuality = value.GetNullableValue(int.TryParse); + jointSomaticNormalQuality = value.GetNullableInt(); break; case "VQSR": recalibratedQuality = value.GetNullableValue(double.TryParse); break; case "CN": // SENECA - copyNumber = value.GetNullableValue(int.TryParse); + copyNumber = value.GetNullableInt(); break; case "DP": // Pisces - depth = value.GetNullableValue(int.TryParse); + depth = value.GetNullableInt(); break; case "CIPOS": - ciPos = value.SplitToArray(',', int.TryParse); + ciPos = value.SplitToArray(); break; case "CIEND": - ciEnd = value.SplitToArray(',', int.TryParse); + ciEnd = value.SplitToArray(); break; case "SVLEN": - svLen = value.GetNullableValue(int.TryParse); + svLen = value.GetNullableInt(); if (svLen != null) svLen = Math.Abs(svLen.Value); break; - case "SVTYPE": - svType = GetSvType(value); - break; + case "SVTYPE": + svType = GetSvType(value); + break; case "END": - end = value.GetNullableValue(int.TryParse); + end = value.GetNullableInt(); + break; + case "INV3": + isInv3 = true; + break; + case "INV5": + isInv5 = true; break; - case "INV3": - isInv3 = true; - break; - case "INV5": - isInv5 = true; - break; - case "ColocalizedCanvas": + case "ColocalizedCanvas": colocalizedWithCnv = true; break; - case "REF": - refRepeatCount = Convert.ToInt32(value); - break; - case "RU": - repeatUnit = value; - break; - } + case "REF": + refRepeatCount = Convert.ToInt32(value); + break; + case "RU": + repeatUnit = value; + break; + } } - var infoData = new InfoData(end, svLen, svType, strandBias, recalibratedQuality, jointSomaticNormalQuality, + var infoData = new InfoData(end, svLen, svType, strandBias, recalibratedQuality, jointSomaticNormalQuality, copyNumber, depth, colocalizedWithCnv, ciPos, ciEnd, isInv3, isInv5, updatedInfoField, repeatUnit, refRepeatCount); return infoData; } - private static VariantType GetSvType(string value) - { - switch (value) - { - case "DEL": - return VariantType.deletion; - case "INS": - return VariantType.insertion; - case "DUP": - return VariantType.duplication; - case "INV": - return VariantType.inversion; - case "TDUP": - return VariantType.tandem_duplication; - case "BND": - return VariantType.translocation_breakend; - case "CNV": - return VariantType.copy_number_variation; - case "STR": - return VariantType.short_tandem_repeat_variation; - case "ALU": - return VariantType.mobile_element_insertion; - case "LINE1": - return VariantType.mobile_element_insertion; - case "LOH": - return VariantType.copy_number_variation; - case "SVA": - return VariantType.mobile_element_insertion; - default: - return VariantType.unknown; - - } - } - - private static Dictionary ExtractInfoFields(string infoField, out string updatedInfoField) + private static VariantType GetSvType(string value) + { + switch (value) + { + case "DEL": + return VariantType.deletion; + case "INS": + return VariantType.insertion; + case "DUP": + return VariantType.duplication; + case "INV": + return VariantType.inversion; + case "TDUP": + return VariantType.tandem_duplication; + case "BND": + return VariantType.translocation_breakend; + case "CNV": + return VariantType.copy_number_variation; + case "STR": + return VariantType.short_tandem_repeat_variation; + case "ALU": + return VariantType.mobile_element_insertion; + case "LINE1": + return VariantType.mobile_element_insertion; + case "LOH": + return VariantType.copy_number_variation; + case "SVA": + return VariantType.mobile_element_insertion; + default: + return VariantType.unknown; + + } + } + + private static Dictionary ExtractInfoFields(string infoField, out string updatedInfoField) { var infoKeyValue = new Dictionary(); @@ -145,22 +153,21 @@ private static Dictionary ExtractInfoFields(string infoField, ou updatedInfoField = ""; return infoKeyValue; } - var infoFields = infoField.Split(';'); + + var infoFields = infoField.OptimizedSplit(';'); var sb = StringBuilderCache.Acquire(); - foreach (var field in infoFields) + foreach (string field in infoFields) { - var keyValue = field.Split('='); - - var key = keyValue[0]; + (string key, string value) = field.OptimizedKeyValue(); if (TagsToRemove.Contains(key)) continue; sb.Append(field); sb.Append(';'); - if (keyValue.Length == 1) infoKeyValue[key] = "true"; - if (keyValue.Length != 1) infoKeyValue[key] = keyValue[1]; + if (value == null) infoKeyValue[key] = "true"; + else infoKeyValue[key] = value; } if (sb.Length > 0) @@ -173,5 +180,5 @@ private static Dictionary ExtractInfoFields(string infoField, ou return infoKeyValue; } - } + } } \ No newline at end of file diff --git a/Vcf/Position.cs b/Vcf/Position.cs index 91b64256..9a06c36a 100644 --- a/Vcf/Position.cs +++ b/Vcf/Position.cs @@ -1,5 +1,5 @@ using System.Linq; -using System.Reflection.Metadata.Ecma335; +using OptimizedCore; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Positions; using VariantAnnotation.Interface.Sequence; @@ -44,22 +44,25 @@ public Position(IChromosome chromosome, int start, int end, string refAllele, st IsRecomposed = isRecomposed; } - public static Position CreatFromSimplePosition(ISimplePosition simplePosition, VariantFactory variantFactory) + public static IPosition ToPosition(ISimplePosition simplePosition, VariantFactory variantFactory) { if (simplePosition == null) return null; - var vcfFields = simplePosition.VcfFields; - var infoData = VcfInfoParser.Parse(vcfFields[VcfCommon.InfoIndex]); - var id = vcfFields[VcfCommon.IdIndex]; - int end = ExtractEnd(infoData, simplePosition.Start, simplePosition.RefAllele.Length); // re-calculate the end by checking INFO field - string[] altAlleles = vcfFields[VcfCommon.AltIndex].Split(',').ToArray(); - double? quality = vcfFields[VcfCommon.QualIndex].GetNullableValue(double.TryParse); - string[] filters = vcfFields[VcfCommon.FilterIndex].Split(';'); - var samples = new SampleFieldExtractor(vcfFields, infoData.Depth).ExtractSamples(); - var variants = variantFactory.CreateVariants(simplePosition.Chromosome, id, simplePosition.Start, end, simplePosition.RefAllele, altAlleles, infoData, simplePosition.IsDecomposed, simplePosition.IsRecomposed); + var vcfFields = simplePosition.VcfFields; + var infoData = VcfInfoParser.Parse(vcfFields[VcfCommon.InfoIndex]); + int end = ExtractEnd(infoData, simplePosition.Start, simplePosition.RefAllele.Length); + var altAlleles = vcfFields[VcfCommon.AltIndex].OptimizedSplit(',').ToArray(); + var quality = vcfFields[VcfCommon.QualIndex].GetNullableValue(double.TryParse); + var filters = vcfFields[VcfCommon.FilterIndex].OptimizedSplit(';'); + var samples = new SampleFieldExtractor(vcfFields, infoData.Depth).ExtractSamples(); - return new Position(simplePosition.Chromosome, simplePosition.Start, end, simplePosition.RefAllele, altAlleles, quality, filters, variants, samples, - infoData, vcfFields, simplePosition.IsDecomposed, simplePosition.IsRecomposed); + var variants = variantFactory.CreateVariants(simplePosition.Chromosome, simplePosition.Start, end, + simplePosition.RefAllele, altAlleles, infoData, simplePosition.IsDecomposed, + simplePosition.IsRecomposed); + + return new Position(simplePosition.Chromosome, simplePosition.Start, end, simplePosition.RefAllele, + altAlleles, quality, filters, variants, samples, infoData, vcfFields, simplePosition.IsDecomposed, + simplePosition.IsRecomposed); } private static int ExtractEnd(IInfoData infoData, int start, int refAlleleLength) @@ -67,7 +70,5 @@ private static int ExtractEnd(IInfoData infoData, int start, int refAlleleLength if (infoData.End != null) return infoData.End.Value; return start + refAlleleLength - 1; } - - } } diff --git a/Vcf/ReadWriteUtilities.cs b/Vcf/ReadWriteUtilities.cs index 8a1e8bbe..b3d37a7c 100644 --- a/Vcf/ReadWriteUtilities.cs +++ b/Vcf/ReadWriteUtilities.cs @@ -22,7 +22,7 @@ public static StreamWriter GetOutputWriter(string outputPath) public static IVcfReader GetVcfReader(string vcfPath, IDictionary chromosomeDictionary, IRefMinorProvider refMinorProvider, bool verboseTranscript, IRecomposer recomposer) { - var useStdInput = vcfPath == "-"; + bool useStdInput = vcfPath == "-"; var peekStream = new PeekStream(useStdInput diff --git a/Vcf/Sample/AlleleDepths.cs b/Vcf/Sample/AlleleDepths.cs index bf4961c1..5fea04b4 100644 --- a/Vcf/Sample/AlleleDepths.cs +++ b/Vcf/Sample/AlleleDepths.cs @@ -1,4 +1,5 @@ using System.Linq; +using OptimizedCore; namespace Vcf.Sample { @@ -54,7 +55,8 @@ private static int[] GetAlleleDepthsUsingAlleleCounts(IntermediateSampleFields i // handle alternate alleles var index = 1; - foreach (var altAllele in intermediateSampleFields.AltAlleles) + + foreach (string altAllele in intermediateSampleFields.AltAlleles) { ac = GetAlleleCountString(altAllele, intermediateSampleFields); if (ac == null) return null; @@ -96,16 +98,22 @@ private static int[] GetAlleleDepthsUsingAlleleCounts(IntermediateSampleFields i /// private static int[] GetAlleleDepthsUsingAd(IntermediateSampleFields intermediateSampleFields) { - if (intermediateSampleFields.FormatIndices.AD == null || intermediateSampleFields.SampleColumns.Length <= intermediateSampleFields.FormatIndices.AD.Value) return null; - var ad = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.AD.Value].Split(','); + if (intermediateSampleFields.FormatIndices.AD == null || intermediateSampleFields.SampleColumns.Length <= + intermediateSampleFields.FormatIndices.AD.Value) return null; + + var ad = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.AD.Value].OptimizedSplit(','); if (ad[0] == ".") return null; - var nAllele = ad.Length; + + int nAllele = ad.Length; var alleleDepths = new int[nAllele]; - for (int i = 0; i < nAllele; i++) + + for (var i = 0; i < nAllele; i++) { - if (!int.TryParse(ad[i], out var num)) return null; - alleleDepths[i] = num; + (int number, bool foundError) = ad[i].OptimizedParseInt32(); + if (foundError) return null; + alleleDepths[i] = number; } + return alleleDepths; } diff --git a/Vcf/Sample/FailedFilter.cs b/Vcf/Sample/FailedFilter.cs index 53d0e24e..fb32d2b4 100644 --- a/Vcf/Sample/FailedFilter.cs +++ b/Vcf/Sample/FailedFilter.cs @@ -2,13 +2,10 @@ { internal static class FailedFilter { - /// - /// returns the failed filter flag - /// public static bool GetFailedFilter(IntermediateSampleFields intermediateSampleFields) { if (intermediateSampleFields.FormatIndices.FT == null) return false; - var filterValue = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.FT.Value]; + string filterValue = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.FT.Value]; return filterValue != "PASS" && filterValue != "."; } } diff --git a/Vcf/Sample/FormatIndices.cs b/Vcf/Sample/FormatIndices.cs index 34592178..c33cb661 100644 --- a/Vcf/Sample/FormatIndices.cs +++ b/Vcf/Sample/FormatIndices.cs @@ -1,4 +1,6 @@ -namespace Vcf.Sample +using OptimizedCore; + +namespace Vcf.Sample { public sealed class FormatIndices { @@ -19,7 +21,7 @@ public sealed class FormatIndices internal int? VF; internal int? MCC; internal int? CN; - internal int? CI;//confidence interval for STRs + internal int? CI; internal int? NR; internal int? NV; internal int? DQ; @@ -35,23 +37,20 @@ public sealed class FormatIndices internal int? DID; internal int? DST; internal int? PCH; - internal int? CHC; - // ReSharper restore InconsistentNaming + internal int? CHC; // PEPE internal int? AQ; internal int? LQ; + // ReSharper restore InconsistentNaming - /// - /// extracts the index from each genotype format field - /// internal static FormatIndices Extract(string formatColumn) { // sanity check: make sure we have a format column if (formatColumn == null) return null; var formatIndices = new FormatIndices(); - var formatCols = formatColumn.Split(':'); + var formatCols = formatColumn.OptimizedSplit(':'); for (var index = 0; index < formatCols.Length; index++) { diff --git a/Vcf/Sample/Genotype.cs b/Vcf/Sample/Genotype.cs index ef787356..25348736 100644 --- a/Vcf/Sample/Genotype.cs +++ b/Vcf/Sample/Genotype.cs @@ -2,15 +2,11 @@ { internal static class Genotype { - /// - /// returns the genotype flag - /// public static string GetGenotype(IntermediateSampleFields intermediateSampleFields) { if (intermediateSampleFields.FormatIndices.GT == null) return null; - var genotype = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.GT.Value]; + string genotype = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.GT.Value]; return genotype == "." ? null : genotype; } - } } diff --git a/Vcf/Sample/GenotypeQuality.cs b/Vcf/Sample/GenotypeQuality.cs index b2669572..c7ff6a04 100644 --- a/Vcf/Sample/GenotypeQuality.cs +++ b/Vcf/Sample/GenotypeQuality.cs @@ -1,23 +1,23 @@ -namespace Vcf.Sample -{ - internal static class GenotypeQuality - { - /// - /// returns the genotype quality given different sources of information - /// - public static int? GetGenotypeQuality(IntermediateSampleFields intermediateSampleFields) - { - var hasGqx = intermediateSampleFields.FormatIndices.GQX != null; - var hasGq = intermediateSampleFields.FormatIndices.GQ != null; - - if (!hasGqx && !hasGq) return null; - - var gqIndex = hasGqx ? intermediateSampleFields.FormatIndices.GQX.Value : intermediateSampleFields.FormatIndices.GQ.Value; - if (intermediateSampleFields.SampleColumns.Length <= gqIndex) return null; - - var gq = intermediateSampleFields.SampleColumns[gqIndex]; - if (int.TryParse(gq, out int num)) return num; - return null; - } - } -} +using OptimizedCore; + +namespace Vcf.Sample +{ + internal static class GenotypeQuality + { + public static int? GetGenotypeQuality(IntermediateSampleFields intermediateSampleFields) + { + bool hasGqx = intermediateSampleFields.FormatIndices.GQX != null; + bool hasGq = intermediateSampleFields.FormatIndices.GQ != null; + + if (!hasGqx && !hasGq) return null; + + int gqIndex = hasGqx ? intermediateSampleFields.FormatIndices.GQX.Value : intermediateSampleFields.FormatIndices.GQ.Value; + if (intermediateSampleFields.SampleColumns.Length <= gqIndex) return null; + + string gq = intermediateSampleFields.SampleColumns[gqIndex]; + + (int number, bool foundError) = gq.OptimizedParseInt32(); + return foundError ? null : (int?)number; + } + } +} diff --git a/Vcf/Sample/IntermediateSampleFields.cs b/Vcf/Sample/IntermediateSampleFields.cs index ec1f8621..29b6b3f7 100644 --- a/Vcf/Sample/IntermediateSampleFields.cs +++ b/Vcf/Sample/IntermediateSampleFields.cs @@ -1,4 +1,5 @@ -using VariantAnnotation.Interface.IO; +using OptimizedCore; +using VariantAnnotation.Interface.IO; namespace Vcf.Sample { @@ -15,11 +16,10 @@ public sealed class IntermediateSampleFields public string RepeatNumber { get; } public string RepeatNumberSpan { get; } public float? DenovoQuality { get; } + // ReSharper disable InconsistentNaming public float? AQ { get; } public float? LQ { get; } public double? VF { get; } - - // ReSharper disable InconsistentNaming public int? TIR { get; } public int? TAR { get; } public int? ACount { get; } @@ -39,10 +39,11 @@ public sealed class IntermediateSampleFields public bool CHC { get; } // ReSharper restore InconsistentNaming + // ReSharper disable once SuggestBaseTypeForParameter public IntermediateSampleFields(string[] vcfColumns, FormatIndices formatIndices, string[] sampleCols) { VcfRefAllele = vcfColumns[VcfCommon.RefIndex]; - AltAlleles = vcfColumns[VcfCommon.AltIndex].Split(','); + AltAlleles = vcfColumns[VcfCommon.AltIndex].OptimizedSplit(','); FormatIndices = formatIndices; SampleColumns = sampleCols; @@ -71,10 +72,11 @@ public IntermediateSampleFields(string[] vcfColumns, FormatIndices formatIndices GetString(formatIndices.GU, sampleCols), GetString(formatIndices.TU, sampleCols)); } + // ReSharper disable once SuggestBaseTypeForParameter private static string GetString(int? index, string[] cols) { if (index == null) return null; - var s = cols[index.Value]; + string s = cols[index.Value]; return s == "." ? null : s; } @@ -87,22 +89,22 @@ internal static bool GetBool(string s, string trueString) internal static float? GetFloat(string s) { if (s == null) return null; - if (float.TryParse(s, out var ret)) return ret; + if (float.TryParse(s, out float ret)) return ret; return null; } - internal static double? GetDouble(string s) + private static double? GetDouble(string s) { if (s == null) return null; - if (double.TryParse(s, out var ret)) return ret; + if (double.TryParse(s, out double ret)) return ret; return null; } internal static int? GetInteger(string s) { if (s == null) return null; - if (int.TryParse(s, out var ret)) return ret; - return null; + (int number, bool foundError) = s.OptimizedParseInt32(); + return foundError ? null : (int?)number; } private static int[] GetIntegers(string s) @@ -111,7 +113,12 @@ private static int[] GetIntegers(string s) if (cols == null) return null; var result = new int[cols.Length]; - for (int i = 0; i < cols.Length; i++) result[i] = int.Parse(cols[i]); + for (var i = 0; i < cols.Length; i++) + { + (int number, bool foundError) = cols[i].OptimizedParseInt32(); + if (foundError) return null; + result[i] = number; + } return result; } @@ -123,7 +130,7 @@ private static (int?, int?) GetLinkedIntegers(string s, string s2) return (num, num2); } - private static string[] GetStrings(string s) => s?.Split(','); + private static string[] GetStrings(string s) => s?.OptimizedSplit(','); private static (int? CopyNumber, string RepeatNumber) GetCopyNumber(string s, bool containsStr) { diff --git a/Vcf/Sample/ReadCounts.cs b/Vcf/Sample/ReadCounts.cs index 287965ee..344585c3 100644 --- a/Vcf/Sample/ReadCounts.cs +++ b/Vcf/Sample/ReadCounts.cs @@ -1,4 +1,6 @@  +using OptimizedCore; + namespace Vcf.Sample { internal static class ReadCounts @@ -6,13 +8,15 @@ internal static class ReadCounts public static int[] GetPairEndReadCounts(IntermediateSampleFields intermediateSampleFields) { if (intermediateSampleFields.FormatIndices.PR == null) return null; - var readCounts = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.PR.Value].Split(','); + var readCounts = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.PR.Value].OptimizedSplit(','); var pairEndReadCounts = new int[readCounts.Length]; - for (int i = 0; i < pairEndReadCounts.Length; i++) + + for (var i = 0; i < pairEndReadCounts.Length; i++) { - if (!int.TryParse(readCounts[i], out var num)) return null; - pairEndReadCounts[i] = num; + (int number, bool foundError) = readCounts[i].OptimizedParseInt32(); + if (foundError) return null; + pairEndReadCounts[i] = number; } return pairEndReadCounts; @@ -21,13 +25,15 @@ public static int[] GetPairEndReadCounts(IntermediateSampleFields intermediateSa public static int[] GetSplitReadCounts(IntermediateSampleFields intermediateSampleFields) { if (intermediateSampleFields.FormatIndices.SR == null) return null; - var splitReadCounts = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.SR.Value].Split(','); + var splitReadCounts = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.SR.Value].OptimizedSplit(','); var splitReads = new int[splitReadCounts.Length]; - for (int i = 0; i < splitReads.Length; i++) + + for (var i = 0; i < splitReads.Length; i++) { - if (!int.TryParse(splitReadCounts[i], out var num)) return null; - splitReads[i] = num; + (int number, bool foundError) = splitReadCounts[i].OptimizedParseInt32(); + if (foundError) return null; + splitReads[i] = number; } return splitReads; diff --git a/Vcf/Sample/SampleFieldExtractor.cs b/Vcf/Sample/SampleFieldExtractor.cs index 198078db..db0cf029 100644 --- a/Vcf/Sample/SampleFieldExtractor.cs +++ b/Vcf/Sample/SampleFieldExtractor.cs @@ -1,4 +1,5 @@ -using VariantAnnotation.Interface.IO; +using OptimizedCore; +using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Positions; namespace Vcf.Sample @@ -15,22 +16,19 @@ internal SampleFieldExtractor(string[] vcfColumns, int? depth = null) _infoDepth = depth; } - /// - /// extracts the genotype fields from the VCF file and returns a list of JSON samples - /// internal ISample[] ExtractSamples() { // sanity check: make sure we have enough columns if (_vcfColumns.Length < VcfCommon.MinNumColumnsSampleGenotypes) return null; - var nSamples = _vcfColumns.Length - VcfCommon.MinNumColumnsSampleGenotypes + 1; + int nSamples = _vcfColumns.Length - VcfCommon.MinNumColumnsSampleGenotypes + 1; var samples = new ISample[nSamples]; // extract the indices for each genotype field _formatIndices = FormatIndices.Extract(_vcfColumns[VcfCommon.FormatIndex]); // add each sample - for (var index = VcfCommon.GenotypeIndex; index < _vcfColumns.Length; index++) + for (int index = VcfCommon.GenotypeIndex; index < _vcfColumns.Length; index++) { samples[index - VcfCommon.GenotypeIndex] = ExtractSample(_vcfColumns[index]); } @@ -38,25 +36,21 @@ internal ISample[] ExtractSamples() return samples; } - /// - /// returns a JsonSample object given the data contained within the sample genotype - /// field. - /// private ISample ExtractSample(string sampleColumn) { // sanity check: make sure we have a format column if (_formatIndices == null || string.IsNullOrEmpty(sampleColumn)) return Sample.EmptySample; - var sampleColumns = sampleColumn.Split(':'); + var sampleColumns = sampleColumn.OptimizedSplit(':'); // handle missing sample columns if (sampleColumns.Length == 1 && sampleColumns[0] == ".") return Sample.EmptySample; var sampleFields = new IntermediateSampleFields(_vcfColumns, _formatIndices, sampleColumns); - var alleleDepths = AlleleDepths.GetAlleleDepths(sampleFields); - var failedFilter = FailedFilter.GetFailedFilter(sampleFields); - var genotype = Genotype.GetGenotype(sampleFields); + var alleleDepths = AlleleDepths.GetAlleleDepths(sampleFields); + bool failedFilter = FailedFilter.GetFailedFilter(sampleFields); + string genotype = Genotype.GetGenotype(sampleFields); var genotypeQuality = GenotypeQuality.GetGenotypeQuality(sampleFields); var totalDepth = TotalDepth.GetTotalDepth(_infoDepth, sampleFields); @@ -64,9 +58,10 @@ private ISample ExtractSample(string sampleColumn) var splitReadCounts = ReadCounts.GetSplitReadCounts(sampleFields); var pairEndReadCounts = ReadCounts.GetPairEndReadCounts(sampleFields); - var isLossOfHeterozygosity = sampleFields.MajorChromosomeCount != null && sampleFields.CopyNumber != null && - sampleFields.MajorChromosomeCount.Value == sampleFields.CopyNumber.Value && - sampleFields.CopyNumber.Value > 1; + bool isLossOfHeterozygosity = sampleFields.MajorChromosomeCount != null && + sampleFields.CopyNumber != null && + sampleFields.MajorChromosomeCount.Value == sampleFields.CopyNumber.Value && + sampleFields.CopyNumber.Value > 1; var sample = new Sample(genotype, genotypeQuality, variantFrequencies, totalDepth, alleleDepths, failedFilter, sampleFields.CopyNumber, isLossOfHeterozygosity, sampleFields.DenovoQuality, splitReadCounts, diff --git a/Vcf/Sample/TotalDepth.cs b/Vcf/Sample/TotalDepth.cs index b3e7c9b9..19f88470 100644 --- a/Vcf/Sample/TotalDepth.cs +++ b/Vcf/Sample/TotalDepth.cs @@ -1,10 +1,9 @@ -namespace Vcf.Sample +using OptimizedCore; + +namespace Vcf.Sample { internal static class TotalDepth { - /// - /// returns the total depth given different sources of information - /// public static int? GetTotalDepth(int? infoDepth, IntermediateSampleFields intermediateSampleFields) { // use TAR & TIR @@ -26,43 +25,24 @@ internal static class TotalDepth return infoDepth; } - /// - /// returns the total depth using TAR & TIR - /// - private static int? GetTotalDepthUsingTarTir(IntermediateSampleFields intermediateSampleFields) - { - return intermediateSampleFields.TAR + intermediateSampleFields.TIR; - } + private static int? GetTotalDepthUsingTarTir(IntermediateSampleFields intermediateSampleFields) => intermediateSampleFields.TAR + intermediateSampleFields.TIR; - /// - /// returns the total depth using tier 1 allele counts - /// - private static int? GetTotalDepthUsingAlleleCounts(IntermediateSampleFields intermediateSampleFields) - { - return intermediateSampleFields.TotalAlleleCount; - } + private static int? GetTotalDepthUsingAlleleCounts(IntermediateSampleFields intermediateSampleFields) => intermediateSampleFields.TotalAlleleCount; - /// - /// returns the total depth using DPI - /// private static int? GetTotalDepthUsingDpi(IntermediateSampleFields intermediateSampleFields) { if (intermediateSampleFields.FormatIndices.DPI == null || intermediateSampleFields.SampleColumns.Length <= intermediateSampleFields.FormatIndices.DPI.Value) return null; - var depth = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.DPI.Value]; - if (int.TryParse(depth, out int num)) return num; - return null; - + string depth = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.DPI.Value]; + (int number, bool foundError) = depth.OptimizedParseInt32(); + return foundError ? null : (int?)number; } - /// - /// returns the total depth using DP - /// private static int? GetTotalDepthUsingDp(IntermediateSampleFields intermediateSampleFields) { if (intermediateSampleFields.FormatIndices.DP == null || intermediateSampleFields.SampleColumns.Length <= intermediateSampleFields.FormatIndices.DP.Value) return null; - var depth = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.DP.Value]; - if (int.TryParse(depth, out int num)) return num; - return null; + string depth = intermediateSampleFields.SampleColumns[intermediateSampleFields.FormatIndices.DP.Value]; + (int number, bool foundError) = depth.OptimizedParseInt32(); + return foundError ? null : (int?)number; } } } diff --git a/Vcf/Sample/VariantFrequency.cs b/Vcf/Sample/VariantFrequency.cs index d5da079b..4c889429 100644 --- a/Vcf/Sample/VariantFrequency.cs +++ b/Vcf/Sample/VariantFrequency.cs @@ -1,4 +1,5 @@ using System.Linq; +using OptimizedCore; namespace Vcf.Sample { @@ -44,13 +45,13 @@ private static double[] GetVariantFrequenciesUsingAlleleCounts(IntermediateSampl if (sampleFields.TotalAlleleCount == null || isReference || !isRefSingleBase || !areAllAltsSingleBase) return null; int numAltAlleles = sampleFields.AltAlleles.Length; - double[] variantFreqs = new double[numAltAlleles]; + var variantFreqs = new double[numAltAlleles]; if (sampleFields.TotalAlleleCount == 0) return variantFreqs; - for (int i = 0; i < numAltAlleles; i++) + for (var i = 0; i < numAltAlleles; i++) { - var alleleCount = GetAlleleCount(sampleFields, i); + int alleleCount = GetAlleleCount(sampleFields, i); variantFreqs[i] = alleleCount / (double)sampleFields.TotalAlleleCount; } @@ -60,8 +61,9 @@ private static double[] GetVariantFrequenciesUsingAlleleCounts(IntermediateSampl private static int GetAlleleCount(IntermediateSampleFields sampleFields, int alleleIndex) { string altAllele = sampleFields.AltAlleles[alleleIndex]; - int alleleCount = 0; + var alleleCount = 0; + // ReSharper disable once SwitchStatementMissingSomeCases switch (altAllele) { case "A": @@ -107,17 +109,17 @@ private static double[] GetVariantFrequenciesUsingAlleleDepths(IntermediateSampl { if (sampleFields.FormatIndices.AD == null || sampleFields.SampleColumns.Length <= sampleFields.FormatIndices.AD.Value) return null; - int numAltAlleles = sampleFields.AltAlleles.Length; - double[] variantFreqs = new double[numAltAlleles]; + int numAltAlleles = sampleFields.AltAlleles.Length; + var variantFreqs = new double[numAltAlleles]; - var adField = sampleFields.SampleColumns[sampleFields.FormatIndices.AD.Value]; - var (alleleDepths, allValuesAreValid, totalDepth) = GetAlleleDepths(adField); + string adField = sampleFields.SampleColumns[sampleFields.FormatIndices.AD.Value]; + (var alleleDepths, bool allValuesAreValid, int totalDepth) = GetAlleleDepths(adField); if (!allValuesAreValid || numAltAlleles != alleleDepths.Length) return null; // sanity check: make sure we handle NaNs properly if (totalDepth == 0) return variantFreqs; - for (int alleleIndex = 0; alleleIndex < numAltAlleles; alleleIndex++) + for (var alleleIndex = 0; alleleIndex < numAltAlleles; alleleIndex++) { variantFreqs[alleleIndex] = alleleDepths[alleleIndex] / (double)totalDepth; } @@ -127,13 +129,14 @@ private static double[] GetVariantFrequenciesUsingAlleleDepths(IntermediateSampl private static (int[] AlleleDepths, bool AllValuesAreValid, int totalDepth) GetAlleleDepths(string adField) { - var adFields = adField.Split(","); + var adFields = adField.OptimizedSplit(','); var alleleDepths = new int[adFields.Length - 1]; - int totalDepth = 0; + var totalDepth = 0; - for (int i = 0; i < adFields.Length; i++) + for (var i = 0; i < adFields.Length; i++) { - if (!int.TryParse(adFields[i], out var ad)) return (null, false, totalDepth); + (int ad, bool foundError) = adFields[i].OptimizedParseInt32(); + if(foundError) return (null, false, totalDepth); if (i > 0) alleleDepths[i - 1] = ad; totalDepth += ad; } diff --git a/Vcf/SimplePosition.cs b/Vcf/SimplePosition.cs index 486040e5..b1018f40 100644 --- a/Vcf/SimplePosition.cs +++ b/Vcf/SimplePosition.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using CommonUtilities; +using OptimizedCore; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Positions; using VariantAnnotation.Interface.Sequence; @@ -26,8 +27,8 @@ public static SimplePosition GetSimplePosition(string[] vcfFields, IDictionary refNameToChromosome) => vcfLine == null ? null : - GetSimplePosition(vcfLine.Split("\t"), refNameToChromosome); + GetSimplePosition(vcfLine.OptimizedSplit('\t'), refNameToChromosome); } } \ No newline at end of file diff --git a/Vcf/StringExtensions.cs b/Vcf/StringExtensions.cs index afdc47ff..d536082c 100644 --- a/Vcf/StringExtensions.cs +++ b/Vcf/StringExtensions.cs @@ -1,4 +1,5 @@ using System; +using OptimizedCore; namespace Vcf { @@ -6,6 +7,12 @@ public static class StringExtensions { public delegate bool TryParse(string str, out T value); + public static int? GetNullableInt(this string str) + { + (int number, bool foundError) = str.OptimizedParseInt32(); + return foundError ? null : (int?) number; + } + public static T? GetNullableValue(this string str, TryParse parseFunc) where T : struct { try @@ -19,28 +26,26 @@ public static class StringExtensions } } - public static T[] SplitToArray(this string str, char separator, TryParse parseFunc) + public static int[] SplitToArray(this string s) { try { - var contents = str.Split(separator); - var vals = new T[contents.Length]; + var cols = s.OptimizedSplit(','); + var values = new int[cols.Length]; - for (int i = 0; i < contents.Length; i++) + for (var i = 0; i < cols.Length; i++) { - if (!parseFunc(contents[i], out T val)) return null; - vals[i] = val; + (int number, bool foundError) = cols[i].OptimizedParseInt32(); + if (foundError) return null; + values[i] = number; } - return vals; + return values; } catch (InvalidCastException) { return null; } } - - - } } \ No newline at end of file diff --git a/Vcf/VariantCreator/CnvCreator.cs b/Vcf/VariantCreator/CnvCreator.cs index 5d952e9a..7769e25b 100644 --- a/Vcf/VariantCreator/CnvCreator.cs +++ b/Vcf/VariantCreator/CnvCreator.cs @@ -1,4 +1,5 @@ -using VariantAnnotation.Interface.Positions; +using OptimizedCore; +using VariantAnnotation.Interface.Positions; using VariantAnnotation.Interface.Sequence; namespace Vcf.VariantCreator @@ -10,41 +11,34 @@ public static class CnvCreator private const string CnvTag = ""; private static readonly AnnotationBehavior VerbosedCnvBehavior = new AnnotationBehavior(false, true, true, false, true, true, true); - public static IVariant Create(IChromosome chromosome, string id, int start, string refAllele, string altAllele, IInfoData infoData, bool enableVerboseTranscript) + + public static IVariant Create(IChromosome chromosome, int start, string refAllele, string altAllele, IInfoData infoData, bool enableVerboseTranscript) { start++; + // CNV caller's can use to indicate a copy number increase where the exact copy number is unknown if (altAllele == "") { - var dupEnd = infoData.End ?? start; - var dupVid = $"{chromosome.EnsemblName}:{start}:{dupEnd}:DUP"; + int dupEnd = infoData.End ?? start; + string dupVid = $"{chromosome.EnsemblName}:{start}:{dupEnd}:DUP"; return new Variant(chromosome, start, dupEnd, refAllele, altAllele, VariantType.copy_number_gain, dupVid, false, false, false, null, null, enableVerboseTranscript ? VerbosedCnvBehavior : CnvBehavior); } var copyNumber = GetCopyNumber(altAllele); + var svType = GetType(copyNumber); + int end = infoData.End ?? start; + string vid = GetVid(chromosome.EnsemblName, start, end, copyNumber); - var svType = GetType(copyNumber); - var end = infoData.End??start; - var vid = GetVid(chromosome.EnsemblName, start, end, copyNumber); - - return new Variant(chromosome, start, end, refAllele, altAllele, svType, vid, false, false, false, null, null, enableVerboseTranscript ? VerbosedCnvBehavior : CnvBehavior); + return new Variant(chromosome, start, end, refAllele, altAllele, svType, vid, false, false, false, null, null, enableVerboseTranscript ? VerbosedCnvBehavior : CnvBehavior); } private static int? GetCopyNumber(string altAllele) { - int? copyNumber; - //if info copy number is null, check the sample copy number - if (altAllele == CnvTag) - { - copyNumber = null; - } - else - { - copyNumber = int.Parse(altAllele.Trim('<', '>').Substring(2)); - } + if (altAllele == CnvTag) return null; - return copyNumber; - } + (int number, bool foundError) = altAllele.Trim('<', '>').Substring(2).OptimizedParseInt32(); + return foundError ? null : (int?)number; + } private static string GetVid(string ensemblName, int start, int end, int? copyNumber) { diff --git a/Vcf/VariantCreator/ReferenceVariantCreator.cs b/Vcf/VariantCreator/ReferenceVariantCreator.cs index 9eb61e2a..3f330fa1 100644 --- a/Vcf/VariantCreator/ReferenceVariantCreator.cs +++ b/Vcf/VariantCreator/ReferenceVariantCreator.cs @@ -10,7 +10,7 @@ public static class ReferenceVariantCreator private static string GetVid(string ensemblName, int start, int end, string refAllele, VariantType variantType) { - var referenceName = ensemblName; + string referenceName = ensemblName; // ReSharper disable once SwitchStatementMissingSomeCases switch (variantType) @@ -30,9 +30,9 @@ private static string GetVid(string ensemblName, int start, int end, string refA public static IVariant Create(IChromosome chromosome, int start, int end, string refallele, string altAllele, string refMinorGlobalMajorAllele) { - var isRefMinor = end == start && refMinorGlobalMajorAllele != null; + bool isRefMinor = end == start && refMinorGlobalMajorAllele != null; var variantType = DetermineVariantType(isRefMinor); - var vid = GetVid(chromosome.EnsemblName, start, end, refallele, variantType); + string vid = GetVid(chromosome.EnsemblName, start, end, refallele, variantType); return isRefMinor ? new Variant(chromosome, start, end, refMinorGlobalMajorAllele, refallele, variantType, vid, diff --git a/Vcf/VariantCreator/RepeatExpansionCreator.cs b/Vcf/VariantCreator/RepeatExpansionCreator.cs index f30074b5..fa01766a 100644 --- a/Vcf/VariantCreator/RepeatExpansionCreator.cs +++ b/Vcf/VariantCreator/RepeatExpansionCreator.cs @@ -1,4 +1,5 @@ -using VariantAnnotation.Interface.Positions; +using OptimizedCore; +using VariantAnnotation.Interface.Positions; using VariantAnnotation.Interface.Sequence; namespace Vcf.VariantCreator @@ -10,16 +11,19 @@ public static IVariant Create(IChromosome chromosome, int start, string refAllel { start++;//for the padding base if (infoData.RefRepeatCount == 0) return null; - - var repeatCount = int.Parse(altAllele.Trim('<', '>').Substring(3)); + + (int number, bool foundError) = altAllele.Trim('<', '>').Substring(3).OptimizedParseInt32(); + if (foundError) return null; + + int repeatCount = number; var svType = repeatCount == infoData.RefRepeatCount ? VariantType.short_tandem_repeat_variation: repeatCount > infoData.RefRepeatCount ? VariantType.short_tandem_repeat_expansion : VariantType.short_tandem_repeat_contraction; - var end = infoData.End ?? 0; - var vid = GetVid(chromosome.EnsemblName, start, end, infoData.RepeatUnit, repeatCount); + int end = infoData.End ?? 0; + string vid = GetVid(chromosome.EnsemblName, start, end, infoData.RepeatUnit, repeatCount); return new Variant(chromosome, start, end, refAllele, altAllele, svType, vid, false, false, false, null, null, RepeatExpansionBehavior); } diff --git a/Vcf/VariantCreator/SmallVariantCreator.cs b/Vcf/VariantCreator/SmallVariantCreator.cs index bf61ea94..03eeec1c 100644 --- a/Vcf/VariantCreator/SmallVariantCreator.cs +++ b/Vcf/VariantCreator/SmallVariantCreator.cs @@ -15,16 +15,17 @@ public static IVariant Create(IChromosome chromosome, int start, string refAllel { if (isDecomposedVar && isRecomposed) throw new InvalidConstraintException("A variant cann't be both decomposed and recomposed"); (start, refAllele, altAllele) = BiDirectionalTrimmer.Trim(start, refAllele, altAllele); - var end = start + refAllele.Length - 1; + int end = start + refAllele.Length - 1; var variantType = GetVariantType(refAllele, altAllele); - var vid = GetVid(chromosome.EnsemblName, start, end, altAllele, variantType); + string vid = GetVid(chromosome.EnsemblName, start, end, altAllele, variantType); - return new Variant(chromosome, start, end, refAllele, altAllele, variantType, vid, false, isDecomposedVar, isRecomposed, null, null, SmallVariantBehavior); - } - private static string GetVid(string ensemblName, int start, int end, string altAllele, VariantType type) - { - var referenceName = ensemblName; + return new Variant(chromosome, start, end, refAllele, altAllele, variantType, vid, false, isDecomposedVar, isRecomposed, null, null, SmallVariantBehavior); + } + + private static string GetVid(string ensemblName, int start, int end, string altAllele, VariantType type) + { + string referenceName = ensemblName; // ReSharper disable once SwitchStatementMissingSomeCases switch (type) @@ -49,18 +50,17 @@ private static string GetInsertedAltAllele(string altAllele) var md5Hash = MD5.Create(); var md5Builder = StringBuilderCache.Acquire(); - - var data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(altAllele)); + var data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(altAllele)); md5Builder.Clear(); - foreach (var b in data) md5Builder.Append(b.ToString("x2")); + foreach (byte b in data) md5Builder.Append(b.ToString("x2")); return StringBuilderCache.GetStringAndRelease(md5Builder); } public static VariantType GetVariantType(string refAllele, string altAllele) { - var referenceAlleleLen = refAllele.Length; - var alternateAlleleLen = altAllele.Length; + int referenceAlleleLen = refAllele.Length; + int alternateAlleleLen = altAllele.Length; if (alternateAlleleLen != referenceAlleleLen) { diff --git a/Vcf/VariantCreator/StructuralVariantCreator.cs b/Vcf/VariantCreator/StructuralVariantCreator.cs index f7d35808..b872cf02 100644 --- a/Vcf/VariantCreator/StructuralVariantCreator.cs +++ b/Vcf/VariantCreator/StructuralVariantCreator.cs @@ -17,19 +17,17 @@ public static class StructuralVariantCreator public static IVariant Create(IChromosome chromosome, int start, string refAllele, string altAllele, IBreakEnd[] breakEnds, IInfoData infoData, bool enableVerboseTranscript) { - var svType = infoData?.SvType ?? VariantType.unknown; if (svType == VariantType.duplication && altAllele == TandemDuplicationAltAllele) svType = VariantType.tandem_duplication; if (svType != VariantType.translocation_breakend) start++; - var end = infoData?.End ?? start; - var vid = GetVid(chromosome.EnsemblName, start, end, svType, breakEnds); + int end = infoData?.End ?? start; + string vid = GetVid(chromosome.EnsemblName, start, end, svType, breakEnds); return new Variant(chromosome, start, end, refAllele, altAllele, svType, vid, false, false, false, null, breakEnds, enableVerboseTranscript ? VerbosedStructuralVariantBehavior : StructuralVariantBehavior); } - private static string GetVid(string ensemblName, int start, int end, VariantType variantType, IReadOnlyList breakEnds) diff --git a/Vcf/VariantCreator/VariantFactory.cs b/Vcf/VariantCreator/VariantFactory.cs index ab73f902..3ea1d6da 100644 --- a/Vcf/VariantCreator/VariantFactory.cs +++ b/Vcf/VariantCreator/VariantFactory.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.RegularExpressions; using CommonUtilities; +using OptimizedCore; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Positions; using VariantAnnotation.Interface.Providers; @@ -16,7 +17,6 @@ public sealed class VariantFactory private readonly IRefMinorProvider _refMinorProvider; private readonly IDictionary _refNameToChromosome; private const string StrPrefix = " refNameToChromosome, IRefMinorProvider refMinorProvider, @@ -43,37 +43,39 @@ private static bool IsBreakend(string[] altAlleles) private static bool IsSymbolicAllele(string altAllele) { - return altAllele.StartsWith("<") && altAllele.EndsWith(">") && !VcfCommon.NonInformativeAltAllele.Contains(altAllele); + return altAllele.OptimizedStartsWith('<') && altAllele.OptimizedEndsWith('>') && !VcfCommon.NonInformativeAltAllele.Contains(altAllele); } - public IVariant[] CreateVariants(IChromosome chromosome, string id, int start, int end, string refAllele, string[] altAlleles, IInfoData infoData, bool[] isDecomposed, bool isRecomposed) + public IVariant[] CreateVariants(IChromosome chromosome, int start, int end, string refAllele, + string[] altAlleles, IInfoData infoData, bool[] isDecomposed, bool isRecomposed) { - var isReference = altAlleles.Length == 1 && VcfCommon.ReferenceAltAllele.Contains(altAlleles[0]); - var isSymbolicAllele = altAlleles.Any(IsSymbolicAllele); - var variantCategory = GetVariantCategory(altAlleles, isReference, isSymbolicAllele, infoData.SvType); + bool isReference = altAlleles.Length == 1 && VcfCommon.ReferenceAltAllele.Contains(altAlleles[0]); + bool isSymbolicAllele = altAlleles.Any(IsSymbolicAllele); + var variantCategory = GetVariantCategory(altAlleles, isReference, isSymbolicAllele, infoData.SvType); - if (isReference) return new[] { GetVariant(chromosome, id, start, end, refAllele, altAlleles[0], infoData, variantCategory, isDecomposed[0], isRecomposed) }; + if (isReference) return new[] { GetVariant(chromosome, start, end, refAllele, altAlleles[0], infoData, variantCategory, isDecomposed[0], isRecomposed) }; var variants = new List(); // ReSharper disable once LoopCanBeConvertedToQuery - for (int i = 0; i < altAlleles.Length; i++) + for (var i = 0; i < altAlleles.Length; i++) { string altAllele = altAlleles[i]; bool isDecomposedVar = isDecomposed[i]; if (VcfCommon.NonInformativeAltAllele.Contains(altAllele)) continue; - variants.Add(GetVariant(chromosome, id, start, end, refAllele, altAllele, infoData, variantCategory, isDecomposedVar, isRecomposed)); + variants.Add(GetVariant(chromosome, start, end, refAllele, altAllele, infoData, variantCategory, isDecomposedVar, isRecomposed)); } return variants.Count == 0 ? null : variants.ToArray(); } - private IVariant GetVariant(IChromosome chromosome, string id, int start, int end, string refAllele, string altAllele, IInfoData infoData, VariantCategory category, bool isDecomposedVar, bool isRecomposed) + private IVariant GetVariant(IChromosome chromosome, int start, int end, string refAllele, string altAllele, + IInfoData infoData, VariantCategory category, bool isDecomposedVar, bool isRecomposed) { switch (category) { case VariantCategory.Reference: - var refMinorGlobalMajorAllele = _refMinorProvider?.GetGlobalMajorAlleleForRefMinor(chromosome, start); + string refMinorGlobalMajorAllele = _refMinorProvider?.GetGlobalMajorAlleleForRefMinor(chromosome, start); return ReferenceVariantCreator.Create(chromosome, start, end, refAllele, altAllele, refMinorGlobalMajorAllele); case VariantCategory.SmallVariant: return SmallVariantCreator.Create(chromosome, start, refAllele, altAllele, isDecomposedVar, isRecomposed); @@ -83,7 +85,7 @@ private IVariant GetVariant(IChromosome chromosome, string id, int start, int en : GetSvBreakEnds(chromosome.EnsemblName, start, infoData.SvType, infoData.End, infoData.IsInv3, infoData.IsInv5); return StructuralVariantCreator.Create(chromosome, start, refAllele, altAllele, svBreakEnds, infoData, _enableVerboseTranscript); case VariantCategory.CNV: - return CnvCreator.Create(chromosome, id, start, refAllele, altAllele, infoData, _enableVerboseTranscript); + return CnvCreator.Create(chromosome, start, refAllele, altAllele, infoData, _enableVerboseTranscript); case VariantCategory.RepeatExpansion: return RepeatExpansionCreator.Create(chromosome, start, refAllele, altAllele, infoData); default: @@ -101,7 +103,7 @@ internal IBreakEnd[] GetSvBreakEnds(string ensemblName, int start, VariantType s { if (svEnd == null) return null; - var end = svEnd.Value; + int end = svEnd.Value; var breakEnds = new IBreakEnd[2]; var chromosome = ReferenceNameUtilities.GetChromosome(_refNameToChromosome, ensemblName); diff --git a/Vcf/Vcf.csproj b/Vcf/Vcf.csproj index 710540f2..7404cc8f 100644 --- a/Vcf/Vcf.csproj +++ b/Vcf/Vcf.csproj @@ -7,6 +7,7 @@ + \ No newline at end of file diff --git a/Vcf/VcfReader.cs b/Vcf/VcfReader.cs index f7a55490..ebaf22b3 100644 --- a/Vcf/VcfReader.cs +++ b/Vcf/VcfReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using OptimizedCore; using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Phantom; using VariantAnnotation.Interface.Positions; @@ -75,7 +76,7 @@ private bool ParseHeader() } // skip headers already produced by Nirvana - var duplicateTag = _nirvanaInfoTags.Any(infoTag => line.StartsWith(infoTag)); + bool duplicateTag = _nirvanaInfoTags.Any(infoTag => line.StartsWith(infoTag)); if (duplicateTag) continue; if (line.StartsWith("##contig=")) IsRcrsMitochondrion = true; @@ -95,20 +96,20 @@ private bool ParseHeader() return hasSampleColumn; } - private bool HasSampleColumn(string line) + private static bool HasSampleColumn(string line) { - string[] vcfHeaderFields = line?.Trim().Split("\t"); + var vcfHeaderFields = line?.Trim().OptimizedSplit('\t'); return vcfHeaderFields?.Length >= VcfCommon.MinNumColumnsSampleGenotypes; } private static string[] ExtractSampleNames(string line) { - var cols = line.Split('\t'); - var hasSampleGenotypes = cols.Length >= VcfCommon.MinNumColumnsSampleGenotypes; + var cols = line.OptimizedSplit('\t'); + bool hasSampleGenotypes = cols.Length >= VcfCommon.MinNumColumnsSampleGenotypes; if (!hasSampleGenotypes) return null; var samplesList = new List(); - for (var i = VcfCommon.GenotypeIndex; i < cols.Length; i++) + for (int i = VcfCommon.GenotypeIndex; i < cols.Length; i++) samplesList.Add(cols[i]); return samplesList.ToArray(); @@ -131,7 +132,7 @@ private ISimplePosition GetNextSimplePosition() return _queuedPositions.Count == 0 ? null: _queuedPositions.Dequeue(); } - public IPosition GetNextPosition() => Position.CreatFromSimplePosition(GetNextSimplePosition(), _variantFactory); + public IPosition GetNextPosition() => Position.ToPosition(GetNextSimplePosition(), _variantFactory); public void Dispose() => _reader?.Dispose(); } diff --git a/Vcf/VcfReaderUtils.cs b/Vcf/VcfReaderUtils.cs index cef6c816..0d172ce2 100644 --- a/Vcf/VcfReaderUtils.cs +++ b/Vcf/VcfReaderUtils.cs @@ -10,7 +10,7 @@ public static class VcfReaderUtils internal static IPosition ParseVcfLine(string vcfLine, VariantFactory variantFactory, IDictionary refNameToChromosome) { var simplePosition = SimplePosition.GetSimplePosition(vcfLine, refNameToChromosome); - return Position.CreatFromSimplePosition(simplePosition, variantFactory); + return Position.ToPosition(simplePosition, variantFactory); } } } \ No newline at end of file From 2001d07b0aa8ac8ae4df22eee5dc06d5f4a4004b Mon Sep 17 00:00:00 2001 From: "Roy, Rajat" Date: Tue, 24 Apr 2018 14:37:57 -0700 Subject: [PATCH 6/7] Feature/tdr jasix 3198 (#175) * using ucsc naming styles since that is the style the json output follows * adding a map of chrom names in index * regenerating jasix index for unit tests * added unit test for query in both enesmbl and ucsc style for on the fly index generation * using tuple names * using TryGetValue * reorganizing the jasix main file * refactoring query processor and adding tests * creating more unit tests * removing commented code --- Jasix/DataStructures/JasixIndex.cs | 4 +- Jasix/IndexCreator.cs | 4 +- Jasix/Jasix.cs | 2 +- Jasix/OnTheFlyIndexCreator.cs | 1 - Jasix/QueryProcessor.cs | 102 ++++++++----------- UnitTests/Jasix/JasixQueryProcessingTests.cs | 13 +-- UnitTests/Jasix/JasixTests.cs | 51 +++++++++- 7 files changed, 97 insertions(+), 80 deletions(-) diff --git a/Jasix/DataStructures/JasixIndex.cs b/Jasix/DataStructures/JasixIndex.cs index 98ba11ad..91da8bf5 100644 --- a/Jasix/DataStructures/JasixIndex.cs +++ b/Jasix/DataStructures/JasixIndex.cs @@ -136,9 +136,7 @@ public IEnumerable GetChromosomeList() public bool ContainsChr(string chr) { - if (_synonymToChrName.TryGetValue(chr, out string indexName)) - return _chrIndices.Keys.Contains(indexName); - return _chrIndices.Keys.Contains(chr); + return _chrIndices.Keys.Contains(_synonymToChrName.TryGetValue(chr, out string indexName) ? indexName : chr); } public string GetIndexChromName(string chromName) diff --git a/Jasix/IndexCreator.cs b/Jasix/IndexCreator.cs index 865795ed..a18852e3 100644 --- a/Jasix/IndexCreator.cs +++ b/Jasix/IndexCreator.cs @@ -106,7 +106,7 @@ public void CreateIndex() line = line.TrimEnd(','); var chrPos = GetChromPosition(line); - CheckFileSorted(chrPos.chr, chrPos.position, previousChr, previousPos); + CheckSorting(chrPos.chr, chrPos.position, previousChr, previousPos); index.Add(chrPos.chr, chrPos.position, chrPos.end, fileLoc); fileLoc = _reader.Position; @@ -143,7 +143,7 @@ private static string ExtractHeader(string line) } // ReSharper disable once UnusedParameter.Local - private void CheckFileSorted(string chr, int pos, string previousChr, int previousPos) + private void CheckSorting(string chr, int pos, string previousChr, int previousPos) { if (chr != previousChr && _processedChromosome.Contains(chr)) { diff --git a/Jasix/Jasix.cs b/Jasix/Jasix.cs index 01ef295e..34f48911 100644 --- a/Jasix/Jasix.cs +++ b/Jasix/Jasix.cs @@ -104,7 +104,7 @@ private static ExitCodes ProgramExecution() if (_printHeaderOnly) { - queryProcessor.PrintHeader(); + queryProcessor.PrintHeaderOnly(); return ExitCodes.Success; } diff --git a/Jasix/OnTheFlyIndexCreator.cs b/Jasix/OnTheFlyIndexCreator.cs index 26a2d1ae..4c12e2cd 100644 --- a/Jasix/OnTheFlyIndexCreator.cs +++ b/Jasix/OnTheFlyIndexCreator.cs @@ -3,7 +3,6 @@ using System.Linq; using ErrorHandling.Exceptions; using Jasix.DataStructures; -using VariantAnnotation.Interface.IO; using VariantAnnotation.Interface.Positions; namespace Jasix diff --git a/Jasix/QueryProcessor.cs b/Jasix/QueryProcessor.cs index e52d09f0..398be7e5 100644 --- a/Jasix/QueryProcessor.cs +++ b/Jasix/QueryProcessor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using Compression.DataStructures; using Jasix.DataStructures; using Newtonsoft.Json; using OptimizedCore; @@ -16,47 +17,18 @@ public sealed class QueryProcessor:IDisposable private readonly Stream _indexStream; private readonly JasixIndex _jasixIndex; - #endregion + #endregion - #region IDisposable + #region IDisposable + public void Dispose() + { + _jsonReader?.Dispose(); + _writer?.Dispose(); + _indexStream?.Dispose(); + } + #endregion - private bool _disposed; - - /// - /// public implementation of Dispose pattern callable by consumers. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - - /// - /// protected implementation of Dispose pattern. - /// - private void Dispose(bool disposing) - { - if (_disposed) - return; - - if (disposing) - { - // Free any other managed objects here. - _jsonReader.Dispose(); - _indexStream.Dispose(); - _writer.Dispose(); - } - - // Free any unmanaged objects here. - // - _disposed = true; - // Free any other managed objects here. - - } - #endregion - - public QueryProcessor(StreamReader jsonReader, Stream indexStream, StreamWriter writer=null) + public QueryProcessor(StreamReader jsonReader, Stream indexStream, StreamWriter writer=null) { _jsonReader = jsonReader; _writer = writer ?? new StreamWriter(Console.OpenStandardOutput()); @@ -70,65 +42,71 @@ public string GetHeader() return _jasixIndex.HeaderLine; } - - public void PrintChromosomeList() + public void PrintChromosomeList() { - foreach (var chrName in _jasixIndex.GetChromosomeList()) + foreach (string chrName in _jasixIndex.GetChromosomeList()) { _writer.WriteLine(chrName); } } - public void PrintHeader() + public void PrintHeaderOnly() { - var headerString = _jasixIndex.HeaderLine; + string headerString = _jasixIndex.HeaderLine; _writer.WriteLine("{" + headerString+"}"); } - public void ProcessQuery(IEnumerable queryStrings, bool printHeader = false) + public int ProcessQuery(IEnumerable queryStrings, bool printHeader = false) { _writer.Write("{"); if (printHeader) { - var headerString = _jasixIndex.HeaderLine; + string headerString = _jasixIndex.HeaderLine; _writer.Write(headerString + ","); } Utilities.PrintQuerySectionOpening(JasixCommons.SectionToIndex, _writer); - foreach (var queryString in queryStrings) + var count = 0; + foreach (string queryString in queryStrings) { var query = Utilities.ParseQuery(queryString); + query.Chromosome = _jasixIndex.GetIndexChromName(query.Chromosome); if (!_jasixIndex.ContainsChr(query.Chromosome)) continue; - var needComma = PrintLargeVariantsExtendingIntoQuery(query); - PrintAllVariantsFromQueryBegin(query, needComma); + + count = PrintLargeVariantsExtendingIntoQuery(query); + count += PrintAllVariantsFromQueryBegin(query, count > 0); } Utilities.PrintQuerySectionClosing(_writer); _writer.WriteLine("}"); + return count; } - private void PrintAllVariantsFromQueryBegin((string, int, int) query, bool needComma) + private int PrintAllVariantsFromQueryBegin((string, int, int) query, bool needComma) { - foreach (var line in ReadOverlappingJsonLines(query)) + var count = 0; + foreach (string line in ReadOverlappingJsonLines(query)) { Utilities.PrintJsonEntry(line, needComma, _writer); needComma = true; + count++; } + return count; } - private bool PrintLargeVariantsExtendingIntoQuery((string, int, int) query) + private int PrintLargeVariantsExtendingIntoQuery((string, int, int) query) { - var needComma = false; - foreach (var line in ReadJsonLinesExtendingInto(query)) + var count = 0; + foreach (string line in ReadJsonLinesExtendingInto(query)) { - Utilities.PrintJsonEntry(line, needComma, _writer); - needComma = true; + Utilities.PrintJsonEntry(line, count>0, _writer); + count++; } - return needComma; + return count; } internal IEnumerable ReadJsonLinesExtendingInto((string Chr, int Start, int End) query) @@ -138,7 +116,7 @@ internal IEnumerable ReadJsonLinesExtendingInto((string Chr, int Start, if (locations == null || locations.Length == 0) yield break; - foreach (var location in locations) + foreach (long location in locations) { RepositionReader(location); @@ -159,7 +137,7 @@ private void RepositionReader(long location) internal IEnumerable ReadOverlappingJsonLines((string Chr, int Start, int End) query) { - var position = _jasixIndex.GetFirstVariantPosition(query.Chr, query.Start, query.End); + long position = _jasixIndex.GetFirstVariantPosition(query.Chr, query.Start, query.End); if (position == -1) yield break; @@ -183,9 +161,8 @@ internal IEnumerable ReadOverlappingJsonLines((string Chr, int Start, in throw; } - string indexChromName = _jasixIndex.GetIndexChromName(query.Chr); string jsonChrom = _jasixIndex.GetIndexChromName(jsonEntry.chromosome); - if (jsonChrom != indexChromName) break; + if (jsonChrom != query.Chr) break; if (jsonEntry.Start > query.End) break; @@ -196,6 +173,7 @@ internal IEnumerable ReadOverlappingJsonLines((string Chr, int Start, in } } - + + } } diff --git a/UnitTests/Jasix/JasixQueryProcessingTests.cs b/UnitTests/Jasix/JasixQueryProcessingTests.cs index c36e4d6a..a99c1171 100644 --- a/UnitTests/Jasix/JasixQueryProcessingTests.cs +++ b/UnitTests/Jasix/JasixQueryProcessingTests.cs @@ -151,18 +151,15 @@ public void Query_onthefly_Ensembl_and_Ucsc() using (var qp = new QueryProcessor(new StreamReader(readStream), indexStream)) { - var ucscResults = qp.ReadOverlappingJsonLines(Utilities.ParseQuery("chr1")); - var ensemblResults = qp.ReadOverlappingJsonLines(Utilities.ParseQuery("1")); + int ucscCount = qp.ProcessQuery(new[] {"chr1"}); + int ensemblCount = qp.ProcessQuery(new[] { "1" }); - int ucscCount = ucscResults.Count(); - int ensemblCount = ensemblResults.Count(); - - Assert.NotEqual(0,ucscCount); - Assert.NotEqual(0, ensemblCount); - Assert.Equal(ucscCount, ensemblCount); + Assert.Equal(13, ucscCount); + Assert.Equal(13, ensemblCount); } } + [Fact] public void Report_overlapping_small_and_extending_large_variants() { diff --git a/UnitTests/Jasix/JasixTests.cs b/UnitTests/Jasix/JasixTests.cs index 1c33487c..b8b96774 100644 --- a/UnitTests/Jasix/JasixTests.cs +++ b/UnitTests/Jasix/JasixTests.cs @@ -1,5 +1,6 @@ using System.IO; using System.IO.Compression; +using System.Text; using Jasix; using Jasix.DataStructures; using VariantAnnotation.Utilities; @@ -21,7 +22,7 @@ public void Query_succeedes_when_it_overlaps_tail_of_previous_bin() chrIndex.Add(i, i + 5, 100_000 + i); } - for (var i = 102 + JasixCommons.PreferredNodeCount; i < 152 + JasixCommons.PreferredNodeCount; i++) + for (int i = 102 + JasixCommons.PreferredNodeCount; i < 152 + JasixCommons.PreferredNodeCount; i++) { chrIndex.Add(i, i + 5, 100_020 + i); } @@ -112,7 +113,7 @@ public void IndexWriteRead() index.Add("chr2", 100, 100, 100150); index.Add("chr2", 102, 105, 100200); - var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + string tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); using (var writer = FileUtilities.GetCreateStream(tempFile)) { @@ -161,7 +162,7 @@ public void JasixStreamReader() public void TestIndexCreation() { var readStream = new BlockGZipStream(ResourceUtilities.GetReadStream(Resources.TopPath("cosmicv72.indels.json.gz")), CompressionMode.Decompress); - var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + string tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); using (var indexCreator = new IndexCreator(readStream, FileUtilities.GetCreateStream(tempFile))) { @@ -180,5 +181,49 @@ public void TestIndexCreation() File.Delete(tempFile); } + + [Fact] + public void GetChromosomeList() + { + var readStream = new BlockGZipStream(ResourceUtilities.GetReadStream(Resources.TopPath("Clinvar20150901.json.gz")), CompressionMode.Decompress); + var indexStream = ResourceUtilities.GetReadStream(Resources.TopPath("Clinvar20150901.json.gz.jsi")); + + var outStream = new MemoryStream(); + using (var writer = new StreamWriter(outStream, Encoding.UTF8, 512, true)) + using (var qp = new QueryProcessor(new StreamReader(readStream), indexStream, writer)) + { + qp.PrintChromosomeList(); + } + + Assert.NotEqual(0, outStream.Length); + outStream.Position = 0; + using (var reader = new StreamReader(outStream)) + { + string chromList = reader.ReadToEnd(); + Assert.Equal("1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\nX\r\nY\r\n", chromList); + } + } + + [Fact] + public void GetHeaderOnly() + { + var readStream = new BlockGZipStream(ResourceUtilities.GetReadStream(Resources.TopPath("Clinvar20150901.json.gz")), CompressionMode.Decompress); + var indexStream = ResourceUtilities.GetReadStream(Resources.TopPath("Clinvar20150901.json.gz.jsi")); + + var outStream = new MemoryStream(); + using (var writer = new StreamWriter(outStream, Encoding.UTF8, 512, true)) + using (var qp = new QueryProcessor(new StreamReader(readStream), indexStream, writer)) + { + qp.PrintHeaderOnly(); + } + + Assert.NotEqual(0, outStream.Length); + outStream.Position = 0; + using (var reader = new StreamReader(outStream)) + { + string header = reader.ReadToEnd(); + Assert.Equal("{{\"header\":{\"annotator\":\"Nirvana 2.0.8.0\",\"creationTime\":\"2018-04-19 16:32:13\",\"genomeAssembly\":\"GRCh37\",\"schemaVersion\":6,\"dataVersion\":\"91.26.44\",\"dataSources\":[{\"name\":\"VEP\",\"version\":\"91\",\"description\":\"Ensembl\",\"releaseDate\":\"2018-03-05\"},{\"name\":\"ClinVar\",\"version\":\"20180129\",\"description\":\"A freely accessible, public archive of reports of the relationships among human variations and phenotypes, with supporting evidence\",\"releaseDate\":\"2018-01-29\"},{\"name\":\"COSMIC\",\"version\":\"84\",\"description\":\"somatic mutation and related details and information relating to human cancers\",\"releaseDate\":\"2018-02-13\"},{\"name\":\"dbSNP\",\"version\":\"150\",\"description\":\"Identifiers for observed variants\",\"releaseDate\":\"2017-04-03\"},{\"name\":\"gnomAD_exome\",\"version\":\"2.0.2\",\"description\":\"Exome allele frequencies from Genome Aggregation Database (gnomAD)\",\"releaseDate\":\"2017-10-05\"},{\"name\":\"gnomAD\",\"version\":\"2.0.2\",\"description\":\"Whole genome allele frequencies from Genome Aggregation Database (gnomAD)\",\"releaseDate\":\"2017-10-05\"},{\"name\":\"MITOMAP\",\"version\":\"20180228\",\"description\":\"Small variants in the MITOMAP human mitochondrial genome database\",\"releaseDate\":\"2018-02-28\"},{\"name\":\"1000 Genomes Project\",\"version\":\"Phase 3 v5a\",\"description\":\"A public catalogue of human variation and genotype data\",\"releaseDate\":\"2013-05-27\"},{\"name\":\"TOPMed\",\"version\":\"freeze_5\",\"description\":\"Allele frequencies from TOPMed data lifted over using dbSNP ids.\",\"releaseDate\":\"2017-08-28\"},{\"name\":\"ClinGen\",\"version\":\"20160414\",\"releaseDate\":\"2016-04-14\"},{\"name\":\"DGV\",\"version\":\"20160515\",\"description\":\"Provides a comprehensive summary of structural variation in the human genome\",\"releaseDate\":\"2016-05-15\"},{\"name\":\"MITOMAP\",\"version\":\"20180228\",\"description\":\"Large structural variants in the MITOMAP human mitochondrial genome database\",\"releaseDate\":\"2018-02-28\"},{\"name\":\"ExAC\",\"version\":\"0.3.1\",\"description\":\"Gene scores from the ExAC project\",\"releaseDate\":\"2016-03-16\"},{\"name\":\"OMIM\",\"version\":\"20180213\",\"description\":\"An Online Catalog of Human Genes and Genetic Disorders\",\"releaseDate\":\"2018-02-13\"},{\"name\":\"phyloP\",\"version\":\"hg19\",\"description\":\"46 way conservation score between humans and 45 other vertebrates\",\"releaseDate\":\"2009-11-10\"}]},\"positions\":[\n}\r\n", header); + } + } } } From 33ad999c18f37855860454658b40c53de92d62e1 Mon Sep 17 00:00:00 2001 From: "Roy, Rajat" Date: Wed, 25 Apr 2018 11:29:14 -0700 Subject: [PATCH 7/7] making jasix index creator null when outputting to std_out (#174) * making jasix index creator null when outputting to std_out * Removed FileUtilities from VariantAnnotation. Applied some ReSharper changes. --- .../CombineCacheDirectoriesMain.cs | 2 +- .../CreateCache/CreateNirvanaDatabaseMain.cs | 1 - CacheUtils/Commands/Download/ExternalFiles.cs | 1 - .../ExtractTranscripts/ExtractTranscriptsMain.cs | 2 +- CacheUtils/Commands/Header/HeaderMain.cs | 2 +- .../ParseVepCacheDirectoryMain.cs | 1 - .../UniversalGeneArchiveMain.cs | 1 - CacheUtils/Genes/DataStores/Hgnc.cs | 2 +- CacheUtils/Helpers/SequenceHelper.cs | 2 +- CacheUtils/Helpers/TranscriptCacheHelper.cs | 2 +- CacheUtils/IntermediateIO/CcdsReader.cs | 1 + CacheUtils/IntermediateIO/LrgReader.cs | 1 + CacheUtils/MiniCache/DataBundle.cs | 4 ++-- Compression/Utilities/FileUtilities.cs | 6 +++--- Jasix/IndexCreator.cs | 2 +- Jasix/Jasix.cs | 1 - Nirvana/Nirvana.cs | 14 ++++++++------ Nirvana/ProviderUtilities.cs | 2 +- Phantom/Workers/Recomposer.cs | 2 +- SAUtils/CreateGnomadTsv/CreateGnomadTsvMain.cs | 2 +- SAUtils/CreateGnomadTsv/GnomadTsvCreator.cs | 1 - .../CreateIntermediateTsvs.cs | 1 - SAUtils/CreateOmimTsv/OmimTsvCreator.cs | 1 - SAUtils/CreateTopMedTsv/CreateTopMedTsvMain.cs | 1 - SAUtils/ExtractCosmicSvs/ExtractCosmicSvsMain.cs | 1 - SAUtils/ExtractMiniSa/MiniSaExtractor.cs | 2 +- SAUtils/ExtractMiniXml/XmlExtractor.cs | 1 - .../InputFileParsers/DataSourceVersionReader.cs | 2 +- .../ParallelIntervalTsvReader.cs | 1 - .../IntermediateAnnotation/ParallelSaTsvReader.cs | 1 - .../IntermediateAnnotation/SaMiscellaniesReader.cs | 1 - SAUtils/MergeInterimTsvs/InterimTsvsMerger.cs | 2 +- SAUtils/SaUtilsCommon.cs | 2 +- SAUtils/TsvWriters/LiteGnomadTsvWriter.cs | 2 +- .../FileHandling/BlockGZipStreamTests.cs | 2 +- .../Compression/FileHandling/BlockStreamTests.cs | 2 +- UnitTests/Jasix/JasixTests.cs | 2 +- UnitTests/TestUtilities/ResourceUtilities.cs | 2 +- .../PhyloP/PhylopAlgorithmsTests.cs | 2 +- VariantAnnotation/PhyloP/PhylopCommon.cs | 2 +- VariantAnnotation/PhyloP/PhylopWriter.cs | 1 - VariantAnnotation/Providers/RefMinorProvider.cs | 2 +- .../Providers/TranscriptAnnotationProvider.cs | 2 +- VariantAnnotation/SaReaderUtils.cs | 2 +- VariantAnnotation/Utilities/FileUtilities.cs | 10 ---------- 45 files changed, 39 insertions(+), 60 deletions(-) delete mode 100644 VariantAnnotation/Utilities/FileUtilities.cs diff --git a/CacheUtils/Commands/CombineCacheDirectories/CombineCacheDirectoriesMain.cs b/CacheUtils/Commands/CombineCacheDirectories/CombineCacheDirectoriesMain.cs index 3ad9adbb..17fa2f06 100644 --- a/CacheUtils/Commands/CombineCacheDirectories/CombineCacheDirectoriesMain.cs +++ b/CacheUtils/Commands/CombineCacheDirectories/CombineCacheDirectoriesMain.cs @@ -10,6 +10,7 @@ using CommandLine.NDesk.Options; using Compression.Algorithms; using Compression.FileHandling; +using Compression.Utilities; using ErrorHandling; using VariantAnnotation.Caches; using VariantAnnotation.Caches.DataStructures; @@ -20,7 +21,6 @@ using VariantAnnotation.IO.Caches; using VariantAnnotation.Logger; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace CacheUtils.Commands.CombineCacheDirectories { diff --git a/CacheUtils/Commands/CreateCache/CreateNirvanaDatabaseMain.cs b/CacheUtils/Commands/CreateCache/CreateNirvanaDatabaseMain.cs index 44a406e9..9f3069b5 100644 --- a/CacheUtils/Commands/CreateCache/CreateNirvanaDatabaseMain.cs +++ b/CacheUtils/Commands/CreateCache/CreateNirvanaDatabaseMain.cs @@ -20,7 +20,6 @@ using VariantAnnotation.IO.Caches; using VariantAnnotation.Logger; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace CacheUtils.Commands.CreateCache { diff --git a/CacheUtils/Commands/Download/ExternalFiles.cs b/CacheUtils/Commands/Download/ExternalFiles.cs index 539531a5..a462eb66 100644 --- a/CacheUtils/Commands/Download/ExternalFiles.cs +++ b/CacheUtils/Commands/Download/ExternalFiles.cs @@ -10,7 +10,6 @@ using VariantAnnotation.Interface; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.Sequence; -using VariantAnnotation.Utilities; namespace CacheUtils.Commands.Download { diff --git a/CacheUtils/Commands/ExtractTranscripts/ExtractTranscriptsMain.cs b/CacheUtils/Commands/ExtractTranscripts/ExtractTranscriptsMain.cs index bc7260d6..d013eded 100644 --- a/CacheUtils/Commands/ExtractTranscripts/ExtractTranscriptsMain.cs +++ b/CacheUtils/Commands/ExtractTranscripts/ExtractTranscriptsMain.cs @@ -8,6 +8,7 @@ using CommandLine.Builders; using CommandLine.NDesk.Options; using CommonUtilities; +using Compression.Utilities; using ErrorHandling; using VariantAnnotation.Caches.DataStructures; using VariantAnnotation.Interface; @@ -19,7 +20,6 @@ using VariantAnnotation.Logger; using VariantAnnotation.Providers; using VariantAnnotation.Sequence; -using VariantAnnotation.Utilities; namespace CacheUtils.Commands.ExtractTranscripts { diff --git a/CacheUtils/Commands/Header/HeaderMain.cs b/CacheUtils/Commands/Header/HeaderMain.cs index 254c617c..0d333284 100644 --- a/CacheUtils/Commands/Header/HeaderMain.cs +++ b/CacheUtils/Commands/Header/HeaderMain.cs @@ -1,11 +1,11 @@ using System; using CommandLine.Builders; using CommandLine.NDesk.Options; +using Compression.Utilities; using ErrorHandling; using ErrorHandling.Exceptions; using VariantAnnotation.IO.Caches; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace CacheUtils.Commands.Header { diff --git a/CacheUtils/Commands/ParseVepCacheDirectory/ParseVepCacheDirectoryMain.cs b/CacheUtils/Commands/ParseVepCacheDirectory/ParseVepCacheDirectoryMain.cs index 12da20b2..7d466684 100644 --- a/CacheUtils/Commands/ParseVepCacheDirectory/ParseVepCacheDirectoryMain.cs +++ b/CacheUtils/Commands/ParseVepCacheDirectory/ParseVepCacheDirectoryMain.cs @@ -15,7 +15,6 @@ using VariantAnnotation.Logger; using VariantAnnotation.Providers; using VariantAnnotation.Sequence; -using VariantAnnotation.Utilities; namespace CacheUtils.Commands.ParseVepCacheDirectory { diff --git a/CacheUtils/Commands/UniversalGeneArchive/UniversalGeneArchiveMain.cs b/CacheUtils/Commands/UniversalGeneArchive/UniversalGeneArchiveMain.cs index b21efdd4..ef1c1367 100644 --- a/CacheUtils/Commands/UniversalGeneArchive/UniversalGeneArchiveMain.cs +++ b/CacheUtils/Commands/UniversalGeneArchive/UniversalGeneArchiveMain.cs @@ -18,7 +18,6 @@ using VariantAnnotation.Interface.Sequence; using VariantAnnotation.Logger; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace CacheUtils.Commands.UniversalGeneArchive { diff --git a/CacheUtils/Genes/DataStores/Hgnc.cs b/CacheUtils/Genes/DataStores/Hgnc.cs index 7b8b2f1f..2752b32f 100644 --- a/CacheUtils/Genes/DataStores/Hgnc.cs +++ b/CacheUtils/Genes/DataStores/Hgnc.cs @@ -5,9 +5,9 @@ using CacheUtils.Genes.DataStructures; using CacheUtils.Genes.IO; using CacheUtils.Genes.Utilities; +using Compression.Utilities; using VariantAnnotation.Algorithms; using VariantAnnotation.Interface.Sequence; -using VariantAnnotation.Utilities; namespace CacheUtils.Genes.DataStores { diff --git a/CacheUtils/Helpers/SequenceHelper.cs b/CacheUtils/Helpers/SequenceHelper.cs index 07d3dbc5..a963f6df 100644 --- a/CacheUtils/Helpers/SequenceHelper.cs +++ b/CacheUtils/Helpers/SequenceHelper.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; +using Compression.Utilities; using VariantAnnotation.Interface.Sequence; using VariantAnnotation.Sequence; -using VariantAnnotation.Utilities; namespace CacheUtils.Helpers { diff --git a/CacheUtils/Helpers/TranscriptCacheHelper.cs b/CacheUtils/Helpers/TranscriptCacheHelper.cs index 8223ae87..b28a600e 100644 --- a/CacheUtils/Helpers/TranscriptCacheHelper.cs +++ b/CacheUtils/Helpers/TranscriptCacheHelper.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using System.IO; +using Compression.Utilities; using VariantAnnotation.Caches; using VariantAnnotation.Interface.Sequence; using VariantAnnotation.IO.Caches; -using VariantAnnotation.Utilities; namespace CacheUtils.Helpers { diff --git a/CacheUtils/IntermediateIO/CcdsReader.cs b/CacheUtils/IntermediateIO/CcdsReader.cs index ec62359f..1c59bd5f 100644 --- a/CacheUtils/IntermediateIO/CcdsReader.cs +++ b/CacheUtils/IntermediateIO/CcdsReader.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using Compression.Utilities; using OptimizedCore; using VariantAnnotation.Utilities; diff --git a/CacheUtils/IntermediateIO/LrgReader.cs b/CacheUtils/IntermediateIO/LrgReader.cs index 62b47fa7..a5e737e8 100644 --- a/CacheUtils/IntermediateIO/LrgReader.cs +++ b/CacheUtils/IntermediateIO/LrgReader.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using Compression.Utilities; using OptimizedCore; using VariantAnnotation.Utilities; diff --git a/CacheUtils/MiniCache/DataBundle.cs b/CacheUtils/MiniCache/DataBundle.cs index db53bbac..af892cd1 100644 --- a/CacheUtils/MiniCache/DataBundle.cs +++ b/CacheUtils/MiniCache/DataBundle.cs @@ -1,9 +1,9 @@ -using VariantAnnotation.Caches.DataStructures; +using Compression.Utilities; +using VariantAnnotation.Caches.DataStructures; using VariantAnnotation.Interface.AnnotatedPositions; using VariantAnnotation.Interface.Sequence; using VariantAnnotation.IO.Caches; using VariantAnnotation.Sequence; -using VariantAnnotation.Utilities; using VC = VariantAnnotation.Caches; namespace CacheUtils.MiniCache diff --git a/Compression/Utilities/FileUtilities.cs b/Compression/Utilities/FileUtilities.cs index a16812a9..a58f4f32 100644 --- a/Compression/Utilities/FileUtilities.cs +++ b/Compression/Utilities/FileUtilities.cs @@ -2,9 +2,9 @@ namespace Compression.Utilities { - internal static class FileUtilities + public static class FileUtilities { - internal static FileStream GetReadStream(string path) => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); - internal static FileStream GetCreateStream(string path) => new FileStream(path, FileMode.Create); + public static FileStream GetReadStream(string path) => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + public static FileStream GetCreateStream(string path) => new FileStream(path, FileMode.Create); } } diff --git a/Jasix/IndexCreator.cs b/Jasix/IndexCreator.cs index a18852e3..bfae2d62 100644 --- a/Jasix/IndexCreator.cs +++ b/Jasix/IndexCreator.cs @@ -4,12 +4,12 @@ using System.IO.Compression; using CommandLine.Utilities; using Compression.FileHandling; +using Compression.Utilities; using ErrorHandling.Exceptions; using Jasix.DataStructures; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OptimizedCore; -using VariantAnnotation.Utilities; namespace Jasix diff --git a/Jasix/Jasix.cs b/Jasix/Jasix.cs index 34f48911..e3d55756 100644 --- a/Jasix/Jasix.cs +++ b/Jasix/Jasix.cs @@ -8,7 +8,6 @@ using ErrorHandling.Exceptions; using Jasix.DataStructures; using VariantAnnotation.Interface; -using VariantAnnotation.Utilities; namespace Jasix { diff --git a/Nirvana/Nirvana.cs b/Nirvana/Nirvana.cs index 11de91d9..2e7c87a6 100644 --- a/Nirvana/Nirvana.cs +++ b/Nirvana/Nirvana.cs @@ -5,6 +5,7 @@ using CommandLine.Utilities; using CommonUtilities; using Compression.FileHandling; +using Compression.Utilities; using ErrorHandling; using Jasix; using Jasix.DataStructures; @@ -73,13 +74,11 @@ private ExitCodes ProgramExecution() using (var jsonWriter = new JsonWriter(outputWriter, _annotatorVersionTag, Date.CurrentTimeStamp, vepDataVersion, dataSourceVersions, sequenceProvider.GenomeAssembly.ToString(), vcfReader.GetSampleNames())) using (var vcfWriter = _vcf ? new LiteVcfWriter(ReadWriteUtilities.GetVcfOutputWriter(_outputFileName), vcfReader.GetHeaderLines(), _annotatorVersionTag, vepDataVersion, dataSourceVersions) : null) using (var gvcfWriter = _gvcf ? new LiteVcfWriter(ReadWriteUtilities.GetGvcfOutputWriter(_outputFileName), vcfReader.GetHeaderLines(), _annotatorVersionTag, vepDataVersion, dataSourceVersions) : null) - using (var jasixIndexCreator = new OnTheFlyIndexCreator(FileUtilities.GetCreateStream(jasixFileName))) + using (var jasixIndexCreator = outputWriter is BgzipTextWriter ? new OnTheFlyIndexCreator(FileUtilities.GetCreateStream(jasixFileName)) : null) { - if (!(outputWriter is BgzipTextWriter bgzipTextWriter)) throw new NullReferenceException("Unable to create the bgzip text writer."); - try { - jasixIndexCreator.SetHeader(jsonWriter.Header); + jasixIndexCreator?.SetHeader(jsonWriter.Header); if (vcfReader.IsRcrsMitochondrion && annotator.GenomeAssembly == GenomeAssembly.GRCh37 || annotator.GenomeAssembly == GenomeAssembly.GRCh38 @@ -90,6 +89,9 @@ private ExitCodes ProgramExecution() IPosition position; var sortedVcfChecker = new SortedVcfChecker(); + var bgzipTextWriter = outputWriter as BgzipTextWriter; + long writerPosition = bgzipTextWriter?.Position ?? -1; + while ((position = vcfReader.GetNextPosition()) != null) { sortedVcfChecker.CheckVcfOrder(position.Chromosome.UcscName); @@ -99,7 +101,7 @@ private ExitCodes ProgramExecution() string json = annotatedPosition.GetJsonString(); - if (json != null) WriteOutput(annotatedPosition, bgzipTextWriter.Position, jasixIndexCreator, jsonWriter, vcfWriter, gvcfWriter, json); + if (json != null) WriteOutput(annotatedPosition, writerPosition, jasixIndexCreator, jsonWriter, vcfWriter, gvcfWriter, json); else gvcfWriter?.Write(string.Join("\t", position.VcfFields)); metrics.Increment(); @@ -123,7 +125,7 @@ private void WriteOutput(IAnnotatedPosition annotatedPosition, long textWriterPo OnTheFlyIndexCreator jasixIndexCreator, IJsonWriter jsonWriter, LiteVcfWriter vcfWriter, LiteVcfWriter gvcfWriter, string jsonOutput) { - jasixIndexCreator.Add(annotatedPosition.Position, textWriterPosition); + jasixIndexCreator?.Add(annotatedPosition.Position, textWriterPosition); jsonWriter.WriteJsonEntry(jsonOutput); if (vcfWriter == null && gvcfWriter == null || annotatedPosition.Position.IsRecomposed) return; diff --git a/Nirvana/ProviderUtilities.cs b/Nirvana/ProviderUtilities.cs index 28739d72..89c8c8ba 100644 --- a/Nirvana/ProviderUtilities.cs +++ b/Nirvana/ProviderUtilities.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Compression.Utilities; using VariantAnnotation; using VariantAnnotation.GeneAnnotation; using VariantAnnotation.Interface; @@ -8,7 +9,6 @@ using VariantAnnotation.Interface.Plugins; using VariantAnnotation.Interface.Providers; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace Nirvana { diff --git a/Phantom/Workers/Recomposer.cs b/Phantom/Workers/Recomposer.cs index c39eb408..1eef0574 100644 --- a/Phantom/Workers/Recomposer.cs +++ b/Phantom/Workers/Recomposer.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; +using Compression.Utilities; using Phantom.DataStructures; using VariantAnnotation.Interface.Phantom; using VariantAnnotation.Interface.Positions; using VariantAnnotation.Interface.Providers; using VariantAnnotation.IO.Caches; -using VariantAnnotation.Utilities; using ReadWriteUtilities = Phantom.Utilities.ReadWriteUtilities; namespace Phantom.Workers diff --git a/SAUtils/CreateGnomadTsv/CreateGnomadTsvMain.cs b/SAUtils/CreateGnomadTsv/CreateGnomadTsvMain.cs index cfd008f0..24314e80 100644 --- a/SAUtils/CreateGnomadTsv/CreateGnomadTsvMain.cs +++ b/SAUtils/CreateGnomadTsv/CreateGnomadTsvMain.cs @@ -3,11 +3,11 @@ using System.IO; using CommandLine.Builders; using CommandLine.NDesk.Options; +using Compression.Utilities; using ErrorHandling; using ErrorHandling.Exceptions; using SAUtils.InputFileParsers; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace SAUtils.CreateGnomadTsv { diff --git a/SAUtils/CreateGnomadTsv/GnomadTsvCreator.cs b/SAUtils/CreateGnomadTsv/GnomadTsvCreator.cs index 47b16bca..1b27889b 100644 --- a/SAUtils/CreateGnomadTsv/GnomadTsvCreator.cs +++ b/SAUtils/CreateGnomadTsv/GnomadTsvCreator.cs @@ -10,7 +10,6 @@ using SAUtils.InputFileParsers; using SAUtils.TsvWriters; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace SAUtils.CreateGnomadTsv { diff --git a/SAUtils/CreateIntermediateTsvs/CreateIntermediateTsvs.cs b/SAUtils/CreateIntermediateTsvs/CreateIntermediateTsvs.cs index 7efb9d0e..826278a0 100644 --- a/SAUtils/CreateIntermediateTsvs/CreateIntermediateTsvs.cs +++ b/SAUtils/CreateIntermediateTsvs/CreateIntermediateTsvs.cs @@ -23,7 +23,6 @@ using VariantAnnotation.Interface.Sequence; using VariantAnnotation.Providers; using VariantAnnotation.Sequence; -using VariantAnnotation.Utilities; namespace SAUtils.CreateIntermediateTsvs { diff --git a/SAUtils/CreateOmimTsv/OmimTsvCreator.cs b/SAUtils/CreateOmimTsv/OmimTsvCreator.cs index 923d7f01..28cc6218 100644 --- a/SAUtils/CreateOmimTsv/OmimTsvCreator.cs +++ b/SAUtils/CreateOmimTsv/OmimTsvCreator.cs @@ -6,7 +6,6 @@ using ErrorHandling; using SAUtils.TsvWriters; using SAUtils.InputFileParsers; -using VariantAnnotation.Utilities; namespace SAUtils.CreateOmimTsv { diff --git a/SAUtils/CreateTopMedTsv/CreateTopMedTsvMain.cs b/SAUtils/CreateTopMedTsv/CreateTopMedTsvMain.cs index 8f262651..d3ce4704 100644 --- a/SAUtils/CreateTopMedTsv/CreateTopMedTsvMain.cs +++ b/SAUtils/CreateTopMedTsv/CreateTopMedTsvMain.cs @@ -4,7 +4,6 @@ using ErrorHandling; using SAUtils.InputFileParsers; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace SAUtils.CreateTopMedTsv { diff --git a/SAUtils/ExtractCosmicSvs/ExtractCosmicSvsMain.cs b/SAUtils/ExtractCosmicSvs/ExtractCosmicSvsMain.cs index 56e1f002..17cc4501 100644 --- a/SAUtils/ExtractCosmicSvs/ExtractCosmicSvsMain.cs +++ b/SAUtils/ExtractCosmicSvs/ExtractCosmicSvsMain.cs @@ -4,7 +4,6 @@ using ErrorHandling; using SAUtils.InputFileParsers; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace SAUtils.ExtractCosmicSvs { diff --git a/SAUtils/ExtractMiniSa/MiniSaExtractor.cs b/SAUtils/ExtractMiniSa/MiniSaExtractor.cs index cb800ec7..196188a4 100644 --- a/SAUtils/ExtractMiniSa/MiniSaExtractor.cs +++ b/SAUtils/ExtractMiniSa/MiniSaExtractor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using Compression.Utilities; using VariantAnnotation.Algorithms; using VariantAnnotation.Interface.Intervals; using VariantAnnotation.Interface.SA; @@ -8,7 +9,6 @@ using VariantAnnotation.IO; using VariantAnnotation.Providers; using VariantAnnotation.SA; -using VariantAnnotation.Utilities; namespace SAUtils.ExtractMiniSa { diff --git a/SAUtils/ExtractMiniXml/XmlExtractor.cs b/SAUtils/ExtractMiniXml/XmlExtractor.cs index 1bcc04b2..edff9a87 100644 --- a/SAUtils/ExtractMiniXml/XmlExtractor.cs +++ b/SAUtils/ExtractMiniXml/XmlExtractor.cs @@ -2,7 +2,6 @@ using System.IO; using System.Xml; using Compression.Utilities; -using VariantAnnotation.Utilities; namespace SAUtils.ExtractMiniXml { diff --git a/SAUtils/InputFileParsers/DataSourceVersionReader.cs b/SAUtils/InputFileParsers/DataSourceVersionReader.cs index f058f5ce..36efcec8 100644 --- a/SAUtils/InputFileParsers/DataSourceVersionReader.cs +++ b/SAUtils/InputFileParsers/DataSourceVersionReader.cs @@ -1,8 +1,8 @@ using System; using System.IO; +using Compression.Utilities; using OptimizedCore; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace SAUtils.InputFileParsers { diff --git a/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelIntervalTsvReader.cs b/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelIntervalTsvReader.cs index bbd6c978..d0136fdf 100644 --- a/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelIntervalTsvReader.cs +++ b/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelIntervalTsvReader.cs @@ -8,7 +8,6 @@ using VariantAnnotation.Interface.SA; using VariantAnnotation.IO; using VariantAnnotation.SA; -using VariantAnnotation.Utilities; namespace SAUtils.InputFileParsers.IntermediateAnnotation { diff --git a/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs b/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs index 5e870727..f9634dc1 100644 --- a/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs +++ b/SAUtils/InputFileParsers/IntermediateAnnotation/ParallelSaTsvReader.cs @@ -5,7 +5,6 @@ using OptimizedCore; using SAUtils.DataStructures; using SAUtils.Interface; -using VariantAnnotation.Utilities; namespace SAUtils.InputFileParsers.IntermediateAnnotation { diff --git a/SAUtils/InputFileParsers/IntermediateAnnotation/SaMiscellaniesReader.cs b/SAUtils/InputFileParsers/IntermediateAnnotation/SaMiscellaniesReader.cs index 9cbc89e6..b27c2835 100644 --- a/SAUtils/InputFileParsers/IntermediateAnnotation/SaMiscellaniesReader.cs +++ b/SAUtils/InputFileParsers/IntermediateAnnotation/SaMiscellaniesReader.cs @@ -5,7 +5,6 @@ using OptimizedCore; using SAUtils.DataStructures; using SAUtils.Interface; -using VariantAnnotation.Utilities; namespace SAUtils.InputFileParsers.IntermediateAnnotation { diff --git a/SAUtils/MergeInterimTsvs/InterimTsvsMerger.cs b/SAUtils/MergeInterimTsvs/InterimTsvsMerger.cs index d7573334..6848f72f 100644 --- a/SAUtils/MergeInterimTsvs/InterimTsvsMerger.cs +++ b/SAUtils/MergeInterimTsvs/InterimTsvsMerger.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using CommandLine.Utilities; +using Compression.Utilities; using SAUtils.DataStructures; using SAUtils.InputFileParsers.IntermediateAnnotation; using SAUtils.Interface; @@ -12,7 +13,6 @@ using VariantAnnotation.Interface.Sequence; using VariantAnnotation.Providers; using VariantAnnotation.SA; -using VariantAnnotation.Utilities; using VariantAnnotation.Interface.GeneAnnotation; namespace SAUtils.MergeInterimTsvs diff --git a/SAUtils/SaUtilsCommon.cs b/SAUtils/SaUtilsCommon.cs index 751e4c55..f8ad72c0 100644 --- a/SAUtils/SaUtilsCommon.cs +++ b/SAUtils/SaUtilsCommon.cs @@ -6,9 +6,9 @@ using CommandLine.Utilities; using CommonUtilities; using Compression.FileHandling; +using Compression.Utilities; using SAUtils.DataStructures; using VariantAnnotation.Interface.Providers; -using VariantAnnotation.Utilities; namespace SAUtils { diff --git a/SAUtils/TsvWriters/LiteGnomadTsvWriter.cs b/SAUtils/TsvWriters/LiteGnomadTsvWriter.cs index 893cb7f1..3798c4fe 100644 --- a/SAUtils/TsvWriters/LiteGnomadTsvWriter.cs +++ b/SAUtils/TsvWriters/LiteGnomadTsvWriter.cs @@ -2,9 +2,9 @@ using System.IO; using System.IO.Compression; using Compression.FileHandling; +using Compression.Utilities; using SAUtils.DataStructures; using VariantAnnotation.Interface.Providers; -using VariantAnnotation.Utilities; namespace SAUtils.TsvWriters { diff --git a/UnitTests/Compression/FileHandling/BlockGZipStreamTests.cs b/UnitTests/Compression/FileHandling/BlockGZipStreamTests.cs index 03483b0b..17060943 100644 --- a/UnitTests/Compression/FileHandling/BlockGZipStreamTests.cs +++ b/UnitTests/Compression/FileHandling/BlockGZipStreamTests.cs @@ -3,9 +3,9 @@ using System.IO.Compression; using System.Text; using Compression.FileHandling; +using Compression.Utilities; using ErrorHandling.Exceptions; using UnitTests.TestUtilities; -using VariantAnnotation.Utilities; using Xunit; namespace UnitTests.Compression.FileHandling diff --git a/UnitTests/Compression/FileHandling/BlockStreamTests.cs b/UnitTests/Compression/FileHandling/BlockStreamTests.cs index cb1fd224..c6855251 100644 --- a/UnitTests/Compression/FileHandling/BlockStreamTests.cs +++ b/UnitTests/Compression/FileHandling/BlockStreamTests.cs @@ -7,6 +7,7 @@ using Compression.Algorithms; using Compression.DataStructures; using Compression.FileHandling; +using Compression.Utilities; using ErrorHandling.Exceptions; using UnitTests.TestUtilities; using VariantAnnotation.Interface.AnnotatedPositions; @@ -14,7 +15,6 @@ using VariantAnnotation.Interface.Sequence; using VariantAnnotation.IO; using VariantAnnotation.IO.Caches; -using VariantAnnotation.Utilities; using Xunit; namespace UnitTests.Compression.FileHandling diff --git a/UnitTests/Jasix/JasixTests.cs b/UnitTests/Jasix/JasixTests.cs index b8b96774..ed522efe 100644 --- a/UnitTests/Jasix/JasixTests.cs +++ b/UnitTests/Jasix/JasixTests.cs @@ -3,10 +3,10 @@ using System.Text; using Jasix; using Jasix.DataStructures; -using VariantAnnotation.Utilities; using Xunit; using UnitTests.TestUtilities; using Compression.FileHandling; +using Compression.Utilities; namespace UnitTests.Jasix { diff --git a/UnitTests/TestUtilities/ResourceUtilities.cs b/UnitTests/TestUtilities/ResourceUtilities.cs index 2b486f29..5da87195 100644 --- a/UnitTests/TestUtilities/ResourceUtilities.cs +++ b/UnitTests/TestUtilities/ResourceUtilities.cs @@ -1,5 +1,5 @@ using System.IO; -using VariantAnnotation.Utilities; +using Compression.Utilities; namespace UnitTests.TestUtilities { diff --git a/UnitTests/VariantAnnotation/PhyloP/PhylopAlgorithmsTests.cs b/UnitTests/VariantAnnotation/PhyloP/PhylopAlgorithmsTests.cs index b9bd1252..e2262b25 100644 --- a/UnitTests/VariantAnnotation/PhyloP/PhylopAlgorithmsTests.cs +++ b/UnitTests/VariantAnnotation/PhyloP/PhylopAlgorithmsTests.cs @@ -1,10 +1,10 @@ using System; using System.IO; +using Compression.Utilities; using UnitTests.TestUtilities; using VariantAnnotation.Interface.Sequence; using VariantAnnotation.PhyloP; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; using Xunit; namespace UnitTests.VariantAnnotation.PhyloP diff --git a/VariantAnnotation/PhyloP/PhylopCommon.cs b/VariantAnnotation/PhyloP/PhylopCommon.cs index a7d1a0ec..7a746a41 100644 --- a/VariantAnnotation/PhyloP/PhylopCommon.cs +++ b/VariantAnnotation/PhyloP/PhylopCommon.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Compression.Utilities; using ErrorHandling.Exceptions; using VariantAnnotation.Interface.Providers; using VariantAnnotation.Interface.Sequence; -using VariantAnnotation.Utilities; namespace VariantAnnotation.PhyloP { diff --git a/VariantAnnotation/PhyloP/PhylopWriter.cs b/VariantAnnotation/PhyloP/PhylopWriter.cs index 7cf4f1c9..55ad1950 100644 --- a/VariantAnnotation/PhyloP/PhylopWriter.cs +++ b/VariantAnnotation/PhyloP/PhylopWriter.cs @@ -9,7 +9,6 @@ using VariantAnnotation.IO; using VariantAnnotation.IO.Caches; using VariantAnnotation.Providers; -using VariantAnnotation.Utilities; namespace VariantAnnotation.PhyloP { diff --git a/VariantAnnotation/Providers/RefMinorProvider.cs b/VariantAnnotation/Providers/RefMinorProvider.cs index 30895b47..2dafc17e 100644 --- a/VariantAnnotation/Providers/RefMinorProvider.cs +++ b/VariantAnnotation/Providers/RefMinorProvider.cs @@ -1,11 +1,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Compression.Utilities; using OptimizedCore; using VariantAnnotation.Interface.Providers; using VariantAnnotation.Interface.Sequence; using VariantAnnotation.SA; -using VariantAnnotation.Utilities; namespace VariantAnnotation.Providers { diff --git a/VariantAnnotation/Providers/TranscriptAnnotationProvider.cs b/VariantAnnotation/Providers/TranscriptAnnotationProvider.cs index 57df7806..4e934652 100644 --- a/VariantAnnotation/Providers/TranscriptAnnotationProvider.cs +++ b/VariantAnnotation/Providers/TranscriptAnnotationProvider.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using CommonUtilities; +using Compression.Utilities; using ErrorHandling.Exceptions; using VariantAnnotation.Algorithms; using VariantAnnotation.AnnotatedPositions; @@ -13,7 +14,6 @@ using VariantAnnotation.Interface.Sequence; using VariantAnnotation.IO.Caches; using VariantAnnotation.TranscriptAnnotation; -using VariantAnnotation.Utilities; namespace VariantAnnotation.Providers { diff --git a/VariantAnnotation/SaReaderUtils.cs b/VariantAnnotation/SaReaderUtils.cs index 0bb511ba..82deffe0 100644 --- a/VariantAnnotation/SaReaderUtils.cs +++ b/VariantAnnotation/SaReaderUtils.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.IO; using System.Linq; +using Compression.Utilities; using ErrorHandling.Exceptions; using VariantAnnotation.GeneAnnotation; using VariantAnnotation.Interface.Providers; @@ -10,7 +11,6 @@ using VariantAnnotation.IO; using VariantAnnotation.Providers; using VariantAnnotation.SA; -using VariantAnnotation.Utilities; namespace VariantAnnotation { diff --git a/VariantAnnotation/Utilities/FileUtilities.cs b/VariantAnnotation/Utilities/FileUtilities.cs deleted file mode 100644 index 81dfee41..00000000 --- a/VariantAnnotation/Utilities/FileUtilities.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.IO; - -namespace VariantAnnotation.Utilities -{ - public static class FileUtilities - { - public static FileStream GetReadStream(string path) => new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); - public static FileStream GetCreateStream(string path) => new FileStream(path, FileMode.Create); - } -} \ No newline at end of file