Skip to content

Commit 17d62a4

Browse files
committed
finish checking in
1 parent c82dae4 commit 17d62a4

File tree

15 files changed

+168
-51
lines changed

15 files changed

+168
-51
lines changed

Ilmn.Das.App.Wittyer.Test/Ilmn.Das.App.Wittyer.Test.csproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,30 @@
5050
<None Update="Resources\WIT-144\truth.vcf">
5151
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
5252
</None>
53+
<None Update="Resources\WIT-149\bed.bed">
54+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
55+
</None>
56+
<None Update="Resources\WIT-149\query.vcf">
57+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
58+
</None>
59+
<None Update="Resources\WIT-149\truth.vcf">
60+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
61+
</None>
62+
<None Update="Resources\Tiny\bed.bed">
63+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
64+
</None>
65+
<None Update="Resources\WIT-154\config.json">
66+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
67+
</None>
68+
<None Update="Resources\WIT-154\bed.bed">
69+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
70+
</None>
71+
<None Update="Resources\WIT-154\query.vcf">
72+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
73+
</None>
74+
<None Update="Resources\WIT-154\truth.vcf">
75+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
76+
</None>
5377
</ItemGroup>
5478

5579
<ItemGroup>

Ilmn.Das.App.Wittyer.Test/Resources/Tiny/query.vcf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
chr1 50000001 . N <DEL> 10 PASS END=50064000;SVTYPE=DUP GT:CN 0/1:1
55
chr1 60000001 . N <DUP> 10 PASS END=60128000;SVTYPE=DUP GT:CN 1/1:4
66
chr1 70000001 . N <DUP> 10 PASS END=70250000;SVTYPE=DUP GT:CN 0/0:2
7-
chr1 80000001 . N <DUP> 10 PASS END=80510000;SVTYPE=DUP GT:CN 0/1:3
7+
chr2 80000001 . N <DUP> 10 PASS END=80510000;SVTYPE=DUP GT:CN 0/1:3
8+
chr2 90000001 . N <DUP> 10 PASS END=90510000;SVTYPE=DUP GT:CN 0/1:3

Ilmn.Das.App.Wittyer.Test/Resources/Tiny/truth.vcf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
chr1 50000001 . N <DEL> 10 PASS END=50064000;SVTYPE=DUP GT:CN 0/1:1
55
chr1 60000001 . N <DUP> 10 PASS END=60128000;SVTYPE=DUP GT:CN 1/1:3
66
chr1 70000001 . N <DUP> 10 PASS END=70250000;SVTYPE=DUP GT:CN 0/0:2
7-
chr1 80000001 . N <DUP> 10 PASS END=80510000;SVTYPE=DUP GT:CN 0/1:3
7+
chr2 80000001 . N <DUP> 10 PASS END=80510000;SVTYPE=DUP GT:CN 0/1:3
8+
chr2 90000001 . N <DUP> 10 PASS END=90510000;SVTYPE=DUP GT:CN 0/1:3

Ilmn.Das.App.Wittyer.Test/SkipBinsTest.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Ilmn.Das.App.Wittyer.Vcf.Variants;
88
using Ilmn.Das.Core.Tries.Extensions;
99
using Ilmn.Das.Std.AppUtils.Misc;
10+
using Ilmn.Das.Std.XunitUtils;
1011
using Xunit;
1112

1213
namespace Ilmn.Das.App.Wittyer.Test
@@ -40,12 +41,13 @@ public void SkippedBinsAreIgnoredInStats()
4041
var (_, query, truth) = MainLauncher.GenerateResults(wittyerSettings).EnumerateSuccesses().First();
4142
var results = MainLauncher.GenerateSampleMetrics(truth, query, false, inputSpecs);
4243

43-
Assert.Equal(1U, results.OverallStats[StatsType.Event].QueryStats.TrueCount);
44-
Assert.Equal(1U, results.OverallStats[StatsType.Event].QueryStats.FalseCount);
45-
Assert.Equal(0.5, results.EventLevelRecallOverall.First(typeRecallTuple => typeRecallTuple.type == WittyerType.CopyNumberGain).recall);
44+
MultiAssert.Equal(2U, results.OverallStats[StatsType.Event].QueryStats.TrueCount);
45+
MultiAssert.Equal(1U, results.OverallStats[StatsType.Event].QueryStats.FalseCount);
46+
MultiAssert.Equal(0.6666666666666666, results.EventLevelRecallOverall.First(typeRecallTuple => typeRecallTuple.type == WittyerType.CopyNumberGain).recall);
4647

4748
var numberOfBinsReportedOn = results.EventLevelRecallPerBin.First().perBinRecall.Count();
48-
Assert.Equal(2, numberOfBinsReportedOn);
49+
MultiAssert.Equal(2, numberOfBinsReportedOn);
50+
MultiAssert.AssertAll();
4951
}
5052
}
5153
}

Ilmn.Das.App.Wittyer.Test/TinyTest.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
using System.Collections.Immutable;
1+
using System.Collections.Generic;
2+
using System.Collections.Immutable;
23
using System.IO;
34
using System.Linq;
45
using Ilmn.Das.App.Wittyer.Infrastructure;
56
using Ilmn.Das.App.Wittyer.Input;
67
using Ilmn.Das.App.Wittyer.Stats;
8+
using Ilmn.Das.App.Wittyer.Vcf.Variants;
79
using Ilmn.Das.Core.Tries.Extensions;
810
using Ilmn.Das.Std.AppUtils.Misc;
11+
using Ilmn.Das.Std.XunitUtils;
912
using Xunit;
1013

1114
namespace Ilmn.Das.App.Wittyer.Test
@@ -17,6 +20,9 @@ public class TinyTest
1720

1821
private static readonly FileInfo TinyQuery =
1922
Path.Combine("Resources", "Tiny", "query.vcf").ToFileInfo();
23+
24+
private static readonly FileInfo TinyBed =
25+
Path.Combine("Resources", "Tiny", "bed.bed").ToFileInfo();
2026

2127
[Fact]
2228
public void CrossType_Works()
@@ -31,7 +37,32 @@ public void CrossType_Works()
3137

3238
var (_, query, truth) = MainLauncher.GenerateResults(wittyerSettings).EnumerateSuccesses().First();
3339
var results = MainLauncher.GenerateSampleMetrics(truth, query, false, inputSpecs);
34-
Assert.Equal(4U, results.OverallStats[StatsType.Event].QueryStats.TrueCount);
40+
MultiAssert.Equal(5U, results.OverallStats[StatsType.Event].QueryStats.TrueCount);
41+
MultiAssert.Equal(1461995U, results.OverallStats[StatsType.Base].QueryStats.TrueCount);
42+
MultiAssert.Equal(1461995U, results.OverallStats[StatsType.Base].TruthStats.TrueCount);
43+
MultiAssert.AssertAll();
44+
}
45+
46+
[Fact]
47+
public void CrossType_Bases_Works()
48+
{
49+
var outputDirectory = Path.GetRandomFileName().ToDirectoryInfo();
50+
var inputSpecs = InputSpec.GenerateDefaultInputSpecs(false)
51+
.Select(i => InputSpec.Create(i.VariantType, i.BinSizes,
52+
10000, i.PercentDistance, i.ExcludedFilters, i.IncludedFilters,
53+
IncludeBedFile.CreateFromBedFile(TinyBed)))
54+
.ToDictionary(i => i.VariantType, i => i);
55+
var wittyerSettings = WittyerSettings.Create(outputDirectory, TinyTruth, TinyQuery,
56+
ImmutableList<ISamplePair>.Empty, EvaluationMode.CrossTypeAndSimpleCounting,
57+
inputSpecs);
58+
59+
var (_, query, truth) = MainLauncher.GenerateResults(wittyerSettings)
60+
.EnumerateSuccesses().First();
61+
var results = MainLauncher.GenerateSampleMetrics(truth, query, false, inputSpecs);
62+
MultiAssert.Equal(4U, results.OverallStats[StatsType.Event].QueryStats.TrueCount);
63+
MultiAssert.Equal(1451995U, results.OverallStats[StatsType.Base].QueryStats.TrueCount);
64+
MultiAssert.Equal(1451995U, results.OverallStats[StatsType.Base].TruthStats.TrueCount);
65+
MultiAssert.AssertAll();
3566
}
3667
}
3768
}

Ilmn.Das.App.Wittyer/Infrastructure/Quantify.cs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,7 @@ private static (IBasicStatsCount overallBaseStats,
145145
var typeTotalTpTrees = perTypeTotalTpDictionary.GetOrAdd(type,
146146
_ => new ConcurrentDictionary<IContigInfo, MergedIntervalTree<uint>>());
147147

148-
foreach (var binGroup in variants.Where(it => // must be Assessed
149-
it.Sample.Wit != WitDecision.NotAssessed)
148+
foreach (var binGroup in variants
150149
.GroupBy(v => v.Win.Start))
151150
{
152151
var binStart = binGroup.Key;
@@ -168,10 +167,15 @@ private static (IBasicStatsCount overallBaseStats,
168167
eventStats.AddTrueEvent();
169168
else if (variant.Sample.Wit == falseDecision)
170169
eventStats.AddFalseEvent();
171-
else if (variant.Sample.Wit != WitDecision.NotAssessed)
170+
else if (variant.Sample.Wit == WitDecision.NotAssessed)
171+
{
172+
// purposely empty, don't do anything on this type but we need to keep this type here
173+
// for base stats.
174+
}
175+
else
172176
throw new InvalidDataException(
173-
$"Unexpected {nameof(WitDecision)} value ({variant.Sample.Wit}) for variant: " +
174-
variant.OriginalVariant.ToShortString());
177+
$"Unexpected {nameof(WitDecision)} value ({variant.Sample.Wit}) for variant: "
178+
+ variant.OriginalVariant.ToShortString());
175179

176180
if (!type.HasBaseLevelStats) continue;
177181

@@ -244,22 +248,22 @@ private static (IBasicStatsCount overallBaseStats,
244248

245249
}
246250

247-
////This is here as sanity check code, we can remove later if we want.
251+
//This is here as sanity check code, we can remove later if we want.
248252

249-
//if (stats == null) // means TotalTree has no intervals
250-
// continue;
253+
if (stats == null) // means TotalTree has no intervals
254+
continue;
251255

252-
//// eventually get rid of this by replacing with actually keeping track of totals in the stats
253-
//// and after outputting stats, we should do sanity check and crash if not equal.
254-
//var fpTotal = stats.FalseCount.Select(kvp => kvp.Value.GetTotalMergedLength()).Sum();
255-
//var tpTotal = stats.TrueCount.Select(kvp => kvp.Value.GetTotalMergedLength()).Sum();
256-
//var expectedTotal = perBinTotalDictionary[binGroup.Key]
257-
// .Select(kvp => kvp.Value.GetTotalMergedLength()).Sum();
258-
//var actualTotal = fpTotal + tpTotal;
256+
// eventually get rid of this by replacing with actually keeping track of totals in the stats
257+
// and after outputting stats, we should do sanity check and crash if not equal.
258+
var fpTotal = stats.FalseCount.Select(kvp => kvp.Value.GetTotalMergedLength()).Sum();
259+
var tpTotal = stats.TrueCount.Select(kvp => kvp.Value.GetTotalMergedLength()).Sum();
260+
var expectedTotal = perBinTotalDictionary[binGroup.Key]
261+
.Select(kvp => kvp.Value.GetTotalMergedLength()).Sum();
262+
var actualTotal = fpTotal + tpTotal;
259263

260-
//if (actualTotal != expectedTotal)
261-
// throw new InvalidDataException(
262-
// $"Expected total bases to be {expectedTotal}, but got {actualTotal}!");
264+
if (actualTotal != expectedTotal)
265+
throw new InvalidDataException(
266+
$"Expected total bases to be {expectedTotal}, but got {actualTotal}!");
263267
}
264268
}
265269

Ilmn.Das.App.Wittyer/Input/IncludeBedFile.cs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,20 @@ public class IncludeBedFile
2424
/// <summary>
2525
/// The IntervalTree from this bed file.
2626
/// </summary>
27-
[NotNull] public readonly GenomeIntervalTree<IContigAndInterval> IntervalTree;
27+
[NotNull]
28+
public GenomeIntervalTree<IContigAndInterval> IntervalTree => _intervalTree.Value;
29+
[NotNull] private readonly Lazy<GenomeIntervalTree<IContigAndInterval>> _intervalTree;
2830

2931
/// <summary>
3032
/// The bed file associated with this instance. If created from <see cref="CreateFromContigIntervals"/>, this will write out a bed file.
3133
/// </summary>
3234
[NotNull] public FileInfo BedFile => _fileSource.Value;
3335
private readonly Lazy<FileInfo> _fileSource;
3436

35-
private IncludeBedFile([NotNull] GenomeIntervalTree<IContigAndInterval> tree,
37+
private IncludeBedFile([NotNull] Lazy<GenomeIntervalTree<IContigAndInterval>> tree,
3638
[NotNull] Lazy<FileInfo> fileSource)
3739
{
38-
IntervalTree = tree;
40+
_intervalTree = tree;
3941
_fileSource = fileSource;
4042
}
4143

@@ -56,7 +58,8 @@ public static IncludeBedFile CreateFromContigIntervals(
5658
{
5759
var tree = contigIntervals as GenomeIntervalTree<IContigAndInterval> ??
5860
CreateGenomeIntervalTree(contigIntervals);
59-
return new IncludeBedFile(tree, CreateBedFileLazy(tree));
61+
return new IncludeBedFile(new Lazy<GenomeIntervalTree<IContigAndInterval>>(tree),
62+
CreateBedFileLazy(tree));
6063

6164
Lazy<FileInfo> CreateBedFileLazy(
6265
IEnumerable<IContigAndInterval> thisTree)
@@ -93,19 +96,29 @@ private static GenomeIntervalTree<IContigAndInterval> CreateGenomeIntervalTree(
9396
var listOrder = new List<IContigInfo>();
9497
foreach (var contigInterval in contigIntervals)
9598
{
96-
if (!dictionary.TryGetValue(contigInterval.Contig, out var tree))
99+
var contig = contigInterval.Contig;
100+
if (!dictionary.TryGetValue(contig, out var tree))
97101
{
98102
tree = MergedIntervalTree<uint>.Create(null);
99-
listOrder.Add(contigInterval.Contig);
100-
dictionary.Add(contigInterval.Contig, tree);
103+
listOrder.Add(contig);
104+
dictionary.Add(contig, tree);
101105
}
102106
tree.Add(contigInterval);
103107
}
104108

105109
var ret = GenomeIntervalTree<IContigAndInterval>.Create();
106110
foreach (var contig in listOrder)
111+
{
107112
ret.AddRange(dictionary[contig]
108-
.Select(i => i as IContigAndInterval ?? ContigAndInterval.Create(contig, i.Start, i.Stop)));
113+
.Select(i => i as IContigAndInterval
114+
?? ContigAndInterval.Create(contig, i.Start, i.Stop)));
115+
var other = contig.ToUcscStyle();
116+
if (other.Name == contig.Name)
117+
other = contig.ToGrchStyle();
118+
if (other.Name != contig.Name)
119+
ret.AddRange(dictionary[contig]
120+
.Select(i => ContigAndInterval.Create(other, i.Start, i.Stop)));
121+
}
109122

110123
return ret;
111124
}
@@ -118,7 +131,10 @@ private static GenomeIntervalTree<IContigAndInterval> CreateGenomeIntervalTree(
118131
[NotNull]
119132
[Pure]
120133
public static IncludeBedFile CreateFromBedFile([NotNull] FileInfo bedFile)
121-
=> CreateFromBedReader(BedReader.Create(bedFile));
134+
=> bedFile.ExistsNow()
135+
? CreateFromBedReader(BedReader.Create(bedFile))
136+
: TypeCache<string, IncludeBedFile>.GetOrAdd(bedFile.FullName,
137+
() => CreateFromBedReader(BedReader.Create(bedFile)));
122138

123139
/// <summary>
124140
/// Creates a new instance of <see cref="IncludeBedFile"/> from a <see cref="BedReader"/>.
@@ -128,7 +144,8 @@ public static IncludeBedFile CreateFromBedFile([NotNull] FileInfo bedFile)
128144
[Pure]
129145
public static IncludeBedFile CreateFromBedReader([NotNull] BedReader bedReader)
130146
=> TypeCache<string, IncludeBedFile>.GetOrAdd(bedReader.FileSource.GetCompleteRealPath().FullName, () =>
131-
new IncludeBedFile(CreateGenomeIntervalTree(bedReader),
147+
new IncludeBedFile(new Lazy<GenomeIntervalTree<IContigAndInterval>>(
148+
() => CreateGenomeIntervalTree(bedReader)),
132149
new Lazy<FileInfo>(() => bedReader.FileSource)));
133150

134151
/// <inheritdoc/>

Ilmn.Das.App.Wittyer/Input/InputParseUtils.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,6 @@ internal static IncludeBedFile ParseBedFile([CanBeNull] string filePath)
9898
if (string.IsNullOrWhiteSpace(filePath))
9999
return null;
100100
var file = filePath.ToFileInfo();
101-
if (!file.ExistsNow())
102-
throw new FileNotFoundException($"{filePath} not found!");
103101
return IncludeBedFile.CreateFromBedFile(file);
104102
}
105103
}

Ilmn.Das.App.Wittyer/Input/InputSpec.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,27 @@ public static IEnumerable<InputSpec> GenerateCustomInputSpecs(bool isCrossTypeOf
182182
includedFilters ?? WittyerConstants.DefaultIncludeFilters,
183183
bedFile));
184184

185+
/// <summary>
186+
/// Creates a new instance of <see cref="InputSpec"/> with a new value for the <see cref="InputSpec.IncludedRegions"/>
187+
/// </summary>
188+
/// <param name="bedFile">The new <see cref="IncludeBedFile"/>, can be null.</param>
189+
[NotNull]
190+
[Pure]
191+
public InputSpec ReplaceBedFile([CanBeNull] IncludeBedFile bedFile)
192+
=> Create(VariantType, BinSizes, BasepairDistance, PercentDistance, ExcludedFilters,
193+
IncludedFilters, bedFile);
194+
195+
/// <summary>
196+
/// Creates an IEnumerable of <see cref="InputSpec"/>s with a possible override of the <see cref="InputSpec.IncludedRegions"/>
197+
/// </summary>
198+
[CanBeNull]
199+
[Pure]
200+
public static IEnumerable<InputSpec> CreateSpecsFromString(
201+
string configText, [CanBeNull] IncludeBedFile bedFileOverride)
202+
=> JsonConvert
203+
.DeserializeObject<IEnumerable<InputSpec>>(configText, InputSpecConverter.Create())
204+
?.Select(x => bedFileOverride == null ? x : x.ReplaceBedFile(bedFileOverride));
205+
185206
[NotNull]
186207
private static IReadOnlyCollection<string> VerifyFiltersAndGetFinalIncluded(
187208
[NotNull] IReadOnlyCollection<string> excludedFilters, [CanBeNull] IReadOnlyCollection<string> includedFilters)

Ilmn.Das.App.Wittyer/Input/WittyerSettings.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,12 @@ internal static IWittyerSettings ParsePrivate(string[] args)
165165

166166
if (parameters._configFile.IsArgumentAssigned)
167167
{
168-
if (parameters._configOptions.Any(x => x.IsArgumentAssigned))
168+
if (parameters._configOptions.Any(x => x.IsArgumentAssigned
169+
&& x != parameters._bedFile))
169170
{
170171
Console.Error.WriteLine(
171172
"Config file argument cannot be used in combination with arguments for bin sizes, basepair distance, " +
172-
"percent distance, included filters, excluded filters, variant types, or include bed. Exiting.");
173+
"percent distance, included filters, excluded filters, or variant types. Exiting.");
173174
Environment.Exit(1);
174175
}
175176

@@ -190,8 +191,8 @@ internal static IWittyerSettings ParsePrivate(string[] args)
190191
Environment.Exit(1);
191192
}
192193

193-
parameters.InputSpecs = JsonConvert
194-
.DeserializeObject<IEnumerable<InputSpec>>(configText, InputSpecConverter.Create())
194+
var bedFile = parameters._bedFile.IsArgumentAssigned ? parameters._bedFile.Argument : null;
195+
parameters.InputSpecs = InputSpec.CreateSpecsFromString(configText, bedFile)
195196
.ToImmutableDictionary(x => x.VariantType, x => x);
196197
}
197198
else

0 commit comments

Comments
 (0)