Skip to content

Commit e5e7d4c

Browse files
tfennecmnbroad
andcommitted
CRAM 3.1 write support: full codecs, profiles, optimisation, and tests
Builds out CRAM 3.1 write support on top of cnorman's initial naive profile, taking htsjdk to feature parity with samtools/htslib for writing CRAM 3.1 and matching samtools on cross-implementation fidelity across a broad range of real-world data. CRAM 3.1 codecs and supporting infrastructure --------------------------------------------- - Full CRAM 3.1 codec set: rANS Nx16 (Order 0/1, RLE, Stripe, Pack, Cat), FQZComp (quality scores), Range adaptive arithmetic coder, and the Name Tokeniser. - New compression profile system (FAST, NORMAL, SMALL, ARCHIVE), matching htslib's `--output-fmt-option fast/small/archive`. Each profile picks per-DataSeries compressors and (for SMALL/ARCHIVE) trial-compression candidate sets. - TrialCompressor: a wrapper that tries multiple compressors per block and keeps the smallest output. Replaces the ad-hoc tag triple- compression path with a uniform, profile-driven mechanism. - DataSeries content IDs aligned with htslib so CRAM dumps from the two implementations are directly comparable. Codec performance work ---------------------- - rANS codecs reworked to use a `byte[]` API and backwards-write, removing per-byte stream overhead and several layers of indirection. - GzipCodec uses Deflater/Inflater directly instead of going through GZIPInputStream/OutputStream, avoiding gzip framing overhead per block. - Name Tokeniser encoder: hand-written tokenisation replaces regex, per-type flags + STRIPE + duplicate-stream elimination + all-MATCH fast path significantly improve both speed and ratio. - FQZComp / Range coder / rANS encoder hot paths tightened. - Pooled RANSNx16Decode instance in the Name Tokeniser to avoid reallocating per call. - Replaced ByteArrayInputStream/OutputStream with unsynchronized CRAMByteReader/Writer to remove monitor overhead in tight loops. - Cached SAMTag key metadata to eliminate per-record String allocation during CRAM decode. - Fused read-base restoration, CIGAR building, and NM/MD computation into a single pass instead of three. - jlibdeflate compatibility fix on the ByteBuffer path. - Roughly 15% faster encoding overall vs the pre-optimisation state, with read decoding gains in line. Correctness fixes found during cross-implementation validation -------------------------------------------------------------- - TLEN is now computed using htslib's coordinate-extent rule (max(end1,end2) - min(start1,start2) + 1, signed by leftmost position) for CRAM-specific compute, rather than the SAM 5'-to-5' rule. Without this, every read with a supplementary alignment mismatched on TLEN through any CRAM round-trip. - CompressionHeader serialisation now uses a growable ByteArrayOutputStream rather than a fixed 100 KB ByteBuffer for the preservation map and tag encoding map. The TD tag dictionary alone can exceed 100 KB for rich tag sets (PacBio/Ultima flow-space, ONT modified-base tags) under the archive profile, where it would previously throw BufferOverflowException. - Slice flush now uses a dual record/bases threshold matching htslib's `seqs_per_slice` AND `bases_per_slice` (default = readsPerSlice * 500). Without this, archive-profile encoding of long-read data (PacBio HiFi 15 kb, ONT 30 kb+) would buffer ~1+ GB of quality scores per slice and OOM the FQZComp encoder. - Strip NM/MD on encode and regenerate them on decode (matching htslib's default `store_nm=0`/`store_md=0` behaviour). Implemented attached mate pairs to align the in-memory representation with the spec. - Restored CIGAR reconstruction when SEQ is `*` (CF_UNKNOWN_BASES). - Fixed crash when reading containers that contain no slices. - Removed unnecessary content digest tags from CRAM slice headers (htslib doesn't write them either; htsjdk's verification on read was overly strict). Tools ----- - `CramConverter`: a small command-line tool for converting between SAM/BAM/CRAM, primarily for benchmarking and exercising profiles. - `CramStats`: dump per-block compression statistics from a CRAM file for debugging and ratio analysis. Tests and CI ------------ - New hts-specs CRAM 3.0/3.1 compliance tests covering decode, index query, and round-trip behaviour. - FQZComp round-trip tests using the hts-specs quality data files. - CRAI index query correctness tests; codec roundtrip property tests. - Split CRAM31 fidelity tests into per-profile classes for parallel execution. - Reduced memory pressure in the unit tests to eliminate intermittent OOM failures on CI. - Sped up several long-running CRAM tests by caching test data, reducing slice-size matrices, and downsampling fixtures. - Misc: fixed a thread-safety bug in VariantContextTestProvider that was producing non-deterministic test counts. CHANGELOG / README updated for the 5.0.0 release. Co-authored-by: Chris Norman <cnorman@broadinstitute.org>
1 parent a9a5825 commit e5e7d4c

202 files changed

Lines changed: 12230 additions & 4328 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,48 @@ early infrastructure for a plugin-based codec framework and resource bundles.
1010

1111
---
1212

13+
## 5.0.0
14+
15+
Adds **CRAM 3.1 write support** to htsjdk. This is the culmination of the read-side codec work
16+
in 4.2.0 and the reader wiring in 4.3.0: htsjdk can now produce CRAM 3.1 files that are
17+
interoperable with samtools/htslib.
18+
19+
### CRAM 3.1 Write Support
20+
21+
- Enable CRAM 3.1 writing with all spec codecs: rANS Nx16, adaptive arithmetic Range coder, FQZComp, Name Tokenisation, and STRIPE
22+
- Add configurable compression profiles (FAST, NORMAL, SMALL, ARCHIVE) with trial compression for automatic codec selection
23+
- Implement `TrialCompressor` to replace ad-hoc triple-compression for tags and align trial candidates with htslib
24+
- Add `GzipCodec` for direct Deflater/Inflater GZIP compression, wired into CRAM as a codec option
25+
- Strip NM/MD tags on CRAM encode and regenerate on decode, matching htslib behavior
26+
- Implement attached (same-slice) mate pair resolution
27+
- Align DataSeries content IDs with htslib for cross-implementation debugging
28+
- Remove content digest tags (BD/SD/B5/S5/B1/S1) from CRAM slice headers, matching htslib/samtools behavior. These are optional per the spec and were expensive to compute. Block-level CRC32 (required by CRAM 3.0+) provides data integrity. This is technically a breaking change but has zero practical impact since no known tools consume these tags.
29+
- Default CRAM version for writing is now 3.1 (was 3.0)
30+
- Add `CramConverter` command-line tool for testing and benchmarking CRAM write profiles
31+
32+
### Codec and Compression Optimizations
33+
34+
- Refactor and optimize all rANS codecs: byte-array API, backwards-write encoding, and general simplifications
35+
- Optimize Name Tokeniser encoder: replace regex with hand-written parser; add per-type flags, STRIPE support, stream deduplication, and all-MATCH elimination
36+
- Optimize FQZComp, Range coder, and rANS encoder hot paths
37+
- Tune NORMAL profile codec assignments based on empirical compression testing
38+
39+
### Performance
40+
41+
- Replace `ByteArrayInputStream`/`ByteArrayOutputStream` with unsynchronized `CRAMByteReader`/`CRAMByteWriter` to eliminate synchronization overhead
42+
- Fuse read base restoration, CIGAR building, and NM/MD computation into a single pass during decode
43+
- Cache tag key metadata to eliminate per-record `String` allocation during CRAM decode
44+
- Pool `RANSNx16Decode` instances in the Name Tokeniser
45+
- Optimize BAM nibble-to-ASCII base decoding with a bulk lookup table
46+
47+
### Testing and Infrastructure
48+
49+
- Split CRAM 3.1 fidelity tests into per-profile classes for parallel execution
50+
- Reduce memory pressure in unit tests to eliminate OOM failures
51+
- Fix thread-safety bug in `VariantContextTestProvider` causing non-deterministic test counts
52+
53+
---
54+
1355
## 4.3.0 (2025-05-09)
1456

1557
Completes CRAM 3.1 read support by wiring the codec implementations (added in 4.2.0) into

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ manipulating HTS data.
1111

1212
> **NOTE: _HTSJDK has only partial support for the latest Variant Call Format Specification. VCFv4.3 can be read but not written, VCFv4.4 can be read in lenient mode only, and there is no support for BCFv2.2._**
1313
14+
> **NOTE: _HTSJDK now supports both reading and writing CRAM 3.1 files. CRAM 3.1 write support includes all codecs defined in the specification (rANS Nx16, adaptive arithmetic Range coder, FQZComp, Name Tokenisation, and STRIPE), configurable compression profiles (FAST, NORMAL, SMALL, ARCHIVE), and trial compression for automatic codec selection. Files produced by htsjdk are interoperable with samtools/htslib._**
15+
1416
### Documentation & Getting Help
1517

1618
API documentation for all versions of HTSJDK since `1.128` are available through [javadoc.io](http://www.javadoc.io/doc/com.github.samtools/htsjdk).

build.gradle

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ tasks.withType(Test).configureEach { task ->
9999

100100
// set heap size for the test JVM(s)
101101
task.minHeapSize = "1G"
102-
task.maxHeapSize = "6G"
102+
task.maxHeapSize = "12G"
103103

104104
task.jvmArgs '-Djava.awt.headless=true' //this prevents awt from displaying a java icon while the tests are running
105105

@@ -160,7 +160,7 @@ test {
160160
excludeGroups "slow", "broken", "defaultReference", "optimistic_vcf_4_4", "ftp", "http", "sra", "ena", "htsget", "unix"
161161
}
162162
parallel = "classes"
163-
threadCount = 2 * Runtime.runtime.availableProcessors()
163+
threadCount = Runtime.runtime.availableProcessors()
164164
}
165165
} dependsOn testWithDefaultReference, testWithOptimisticVCF4_4
166166

@@ -213,6 +213,12 @@ spotbugsTest {
213213
}
214214
}
215215

216+
// Fat JAR with all dependencies for standalone CLI tools (CramConverter, CramComparison, etc.)
217+
shadowJar {
218+
archiveClassifier.set('all')
219+
mergeServiceFiles()
220+
}
221+
216222
publishing {
217223
publications {
218224
htsjdk(MavenPublication) {
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package htsjdk.samtools;
2+
3+
import java.io.File;
4+
5+
/**
6+
* Simple command-line tool for reading and optionally converting BAM files, primarily
7+
* for experimenting with BAM read-path profiling.
8+
*
9+
* <p>Usage:
10+
* <pre>
11+
* java -cp htsjdk.jar htsjdk.samtools.BamConverter input.bam [output.bam]
12+
* </pre>
13+
*
14+
* <p>If no output is specified, records are read and iterated but not written.
15+
*/
16+
public class BamConverter {
17+
18+
private static final String USAGE = String.join("\n",
19+
"Usage: BamConverter <input> [output]",
20+
"",
21+
"Read and optionally convert a BAM file.",
22+
"",
23+
"Arguments:",
24+
" input Input BAM file",
25+
" output Optional output BAM file (omit to read-only)"
26+
);
27+
28+
/**
29+
* Entry point. Parses command-line arguments and performs the read/conversion.
30+
*
31+
* @param args command-line arguments (see USAGE for details)
32+
*/
33+
public static void main(final String[] args) {
34+
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
35+
System.out.println(USAGE);
36+
System.exit(0);
37+
}
38+
if (args.length < 1) {
39+
System.err.println(USAGE);
40+
System.exit(1);
41+
}
42+
43+
final boolean eager = hasFlag(args, "--eager");
44+
// Collect positional args (non-flag arguments)
45+
final String[] positional = java.util.Arrays.stream(args)
46+
.filter(a -> !a.startsWith("--"))
47+
.toArray(String[]::new);
48+
if (positional.length < 1) {
49+
System.err.println(USAGE);
50+
System.exit(1);
51+
}
52+
final String inputPath = positional[0];
53+
final String outputPath = positional.length > 1 ? positional[1] : null;
54+
55+
if (outputPath != null) {
56+
System.err.printf("Converting %s -> %s%s%n", inputPath, outputPath, eager ? " (eager decode)" : "");
57+
} else {
58+
System.err.printf("Reading %s (no output%s)%n", inputPath, eager ? ", eager decode" : "");
59+
}
60+
61+
final SamReaderFactory readerFactory = SamReaderFactory.makeDefault()
62+
.validationStringency(ValidationStringency.SILENT);
63+
64+
long count = 0;
65+
final long startTime = System.currentTimeMillis();
66+
67+
try (final SamReader reader = readerFactory.open(new File(inputPath))) {
68+
final SAMFileHeader header = reader.getFileHeader();
69+
70+
if (outputPath != null) {
71+
final SAMFileWriterFactory writerFactory = new SAMFileWriterFactory();
72+
try (final SAMFileWriter writer = writerFactory.makeBAMWriter(header, true, new File(outputPath).toPath())) {
73+
for (final SAMRecord record : reader) {
74+
if (eager) record.eagerDecode();
75+
writer.addAlignment(record);
76+
count++;
77+
if (count % 1_000_000 == 0) {
78+
System.err.printf(" ... %,d records%n", count);
79+
}
80+
}
81+
}
82+
} else {
83+
for (final SAMRecord record : reader) {
84+
if (eager) record.eagerDecode();
85+
count++;
86+
if (count % 1_000_000 == 0) {
87+
System.err.printf(" ... %,d records%n", count);
88+
}
89+
}
90+
}
91+
} catch (final Exception e) {
92+
die("Error: " + e.getMessage());
93+
}
94+
95+
final long elapsed = System.currentTimeMillis() - startTime;
96+
final long inputSize = new File(inputPath).length();
97+
98+
if (outputPath != null) {
99+
final long outputSize = new File(outputPath).length();
100+
System.err.printf("Done. %,d records in %.1fs. Input: %,d bytes, Output: %,d bytes (%.1f%%)%n",
101+
count, elapsed / 1000.0, inputSize, outputSize,
102+
inputSize > 0 ? (100.0 * outputSize / inputSize) : 0);
103+
} else {
104+
System.err.printf("Done. %,d records in %.1fs. Input: %,d bytes%n",
105+
count, elapsed / 1000.0, inputSize);
106+
}
107+
}
108+
109+
private static boolean hasFlag(final String[] args, final String flag) {
110+
for (final String arg : args) {
111+
if (flag.equals(arg)) return true;
112+
}
113+
return false;
114+
}
115+
116+
private static void die(final String message) {
117+
System.err.println("ERROR: " + message);
118+
System.err.println();
119+
System.err.println(USAGE);
120+
System.exit(1);
121+
}
122+
}

src/main/java/htsjdk/samtools/CRAMContainerStreamWriter.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import htsjdk.samtools.cram.build.ContainerFactory;
44
import htsjdk.samtools.cram.build.CramIO;
5-
import htsjdk.samtools.cram.common.CramVersions;
5+
import htsjdk.samtools.cram.common.CRAMVersion;
66
import htsjdk.samtools.cram.ref.CRAMReferenceSource;
77
import htsjdk.samtools.cram.structure.*;
88
import htsjdk.samtools.util.RuntimeIOException;
@@ -19,6 +19,7 @@ public class CRAMContainerStreamWriter {
1919
private final SAMFileHeader samFileHeader;
2020
private final ContainerFactory containerFactory;
2121
private final CRAMIndexer cramIndexer;
22+
private final CRAMVersion cramVersion;
2223

2324
private long streamOffset = 0;
2425

@@ -70,7 +71,7 @@ public CRAMContainerStreamWriter(
7071
* Create a CRAMContainerStreamWriter for writing SAM records into a series of CRAM
7172
* containers on output stream, with an optional index.
7273
*
73-
* @param encodingStrategy encoding strategy values
74+
* @param encodingStrategy encoding strategy values (includes CRAM version)
7475
* @param referenceSource reference cramReferenceSource
7576
* @param samFileHeader {@link SAMFileHeader} to be used. Sort order is determined by the sortOrder property of this arg.
7677
* @param outputStream where to write the CRAM stream.
@@ -88,6 +89,7 @@ public CRAMContainerStreamWriter(
8889
this.outputStream = outputStream;
8990
this.cramIndexer = indexer;
9091
this.outputStreamIdentifier = outputIdentifier;
92+
this.cramVersion = encodingStrategy.getCramVersion();
9193
this.containerFactory = new ContainerFactory(samFileHeader, encodingStrategy, referenceSource);
9294
}
9395

@@ -103,11 +105,11 @@ public void writeAlignment(final SAMRecord alignment) {
103105
}
104106

105107
/**
106-
* Write a CRAM file header and the previously provided SAM header to the stream.
108+
* Write a CRAM file header and the provided SAM header to the stream.
109+
* Retained for backward compatibility with external projects (disq, GATK).
107110
*/
108-
// TODO: retained for backward compatibility for disq in order to run GATK tests (remove before merging this branch)
109111
public void writeHeader(final SAMFileHeader requestedSAMFileHeader) {
110-
final CramHeader cramHeader = new CramHeader(CramVersions.DEFAULT_CRAM_VERSION, outputStreamIdentifier);
112+
final CramHeader cramHeader = new CramHeader(cramVersion, outputStreamIdentifier);
111113
streamOffset = CramIO.writeCramHeader(cramHeader, outputStream);
112114
streamOffset += Container.writeSAMFileHeaderContainer(cramHeader.getCRAMVersion(), requestedSAMFileHeader, outputStream);
113115
}
@@ -131,7 +133,7 @@ public void finish(final boolean writeEOFContainer) {
131133
writeContainer(container);
132134
}
133135
if (writeEOFContainer) {
134-
CramIO.writeCramEOF(CramVersions.DEFAULT_CRAM_VERSION, outputStream);
136+
CramIO.writeCramEOF(cramVersion, outputStream);
135137
}
136138
outputStream.flush();
137139
if (cramIndexer != null) {
@@ -144,7 +146,7 @@ public void finish(final boolean writeEOFContainer) {
144146
}
145147

146148
protected void writeContainer(final Container container) {
147-
streamOffset += container.write(CramVersions.DEFAULT_CRAM_VERSION, outputStream);
149+
streamOffset += container.write(cramVersion, outputStream);
148150
if (cramIndexer != null) {
149151
// using silent validation here because the reads have been through validation already or
150152
// they have been generated somehow through the htsjdk

src/main/java/htsjdk/samtools/CRAMFileReader.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,11 @@ public CloseableIterator<SAMRecord> queryUnmapped() {
374374
try {
375375
seekableStream.seek(0);
376376
iterator = new CRAMIterator(seekableStream, referenceSource, validationStringency);
377-
seekableStream.seek(startOfLastLinearBin >>> 16);
377+
// When startOfLastLinearBin is -1, there are no mapped reads and the entire file is
378+
// unmapped. In that case, iterate from the beginning (already at position 0).
379+
if (startOfLastLinearBin != -1) {
380+
seekableStream.seek(startOfLastLinearBin >>> 16);
381+
}
378382
boolean atAlignments;
379383
do {
380384
atAlignments = iterator.advanceToAlignmentInContainer(SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX, SAMRecord.NO_ALIGNMENT_START);

src/main/java/htsjdk/samtools/CRAMIterator.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,11 @@ private BAMIteratorFilter.FilteringIteratorState nextContainer() {
115115
compressorCache,
116116
getSAMFileHeader());
117117
samRecordIterator = samRecords.iterator();
118-
return BAMIteratorFilter.FilteringIteratorState.MATCHES_FILTER;
118+
// A container may match the query but produce no records (e.g. a container with
119+
// only a compression header and no slices). Skip to the next container in that case.
120+
return samRecords.isEmpty()
121+
? BAMIteratorFilter.FilteringIteratorState.CONTINUE_ITERATION
122+
: BAMIteratorFilter.FilteringIteratorState.MATCHES_FILTER;
119123
} else {
120124
return BAMIteratorFilter.FilteringIteratorState.CONTINUE_ITERATION;
121125
}

src/main/java/htsjdk/samtools/SAMFileWriterFactory.java

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class SAMFileWriterFactory implements Cloneable {
6060
private SamFlagField samFlagFieldOutput = SamFlagField.NONE;
6161
private Integer maxRecordsInRam = null;
6262
private DeflaterFactory deflaterFactory = BlockCompressedOutputStream.getDefaultDeflaterFactory();
63+
private CRAMEncodingStrategy cramEncodingStrategy = new CRAMEncodingStrategy();
6364

6465
/** simple constructor */
6566
public SAMFileWriterFactory() {
@@ -76,6 +77,7 @@ public SAMFileWriterFactory( final SAMFileWriterFactory other) {
7677
this.tmpDir = other.tmpDir;
7778
this.compressionLevel = other.compressionLevel;
7879
this.maxRecordsInRam = other.maxRecordsInRam;
80+
this.cramEncodingStrategy = other.cramEncodingStrategy;
7981
}
8082

8183
@Override
@@ -241,6 +243,30 @@ public SAMFileWriterFactory setSamFlagFieldOutput(final SamFlagField samFlagFiel
241243
return this;
242244
}
243245

246+
/**
247+
* Set the {@link CRAMEncodingStrategy} to use when creating CRAM writers. Controls the CRAM version,
248+
* compression profile, and per-data-series codec selection.
249+
*
250+
* <p>The default strategy uses the {@link htsjdk.samtools.cram.structure.CRAMCompressionProfile#NORMAL} profile.
251+
* To use a specific profile:
252+
* <pre>
253+
* factory.setCRAMEncodingStrategy(CRAMCompressionProfile.ARCHIVE.toStrategy());
254+
* </pre>
255+
*
256+
* @param cramEncodingStrategy the encoding strategy to use for CRAM output
257+
* @return this factory for chaining
258+
*/
259+
public SAMFileWriterFactory setCRAMEncodingStrategy(final CRAMEncodingStrategy cramEncodingStrategy) {
260+
if (cramEncodingStrategy == null) throw new IllegalArgumentException("CRAM encoding strategy was null");
261+
this.cramEncodingStrategy = cramEncodingStrategy;
262+
return this;
263+
}
264+
265+
/** @return the current CRAM encoding strategy */
266+
public CRAMEncodingStrategy getCRAMEncodingStrategy() {
267+
return cramEncodingStrategy;
268+
}
269+
244270
/**
245271
* Create a BAMFileWriter that is ready to receive SAMRecords. Uses default compression level.
246272
*
@@ -524,8 +550,14 @@ public CRAMFileWriter makeCRAMWriter(final SAMFileHeader header, final OutputStr
524550
* @return CRAMFileWriter
525551
*/
526552
public CRAMFileWriter makeCRAMWriter(final SAMFileHeader header, final OutputStream stream, final Path referenceFasta) {
527-
// create the CRAMFileWriter directly without propagating factory settings
528-
return new CRAMFileWriter(stream, new ReferenceSource(referenceFasta), header, null);
553+
return new CRAMFileWriter(
554+
cramEncodingStrategy,
555+
stream,
556+
null, // no index
557+
true, // presorted
558+
new ReferenceSource(referenceFasta),
559+
header,
560+
null);
529561
}
530562

531563
/**
@@ -670,23 +702,17 @@ private CRAMFileWriter createCRAMWriterWithSettings(
670702

671703
final Path md5Path = IOUtil.addExtension(outputFile, ".md5");
672704
final CRAMFileWriter writer = new CRAMFileWriter(
705+
cramEncodingStrategy,
673706
createMd5File ? new Md5CalculatingOutputStream(cramOS, md5Path) : cramOS,
674707
indexOS,
675708
presorted,
676709
referenceSource,
677710
header,
678711
outputFile.toUri().toString());
679-
setCRAMWriterDefaults(writer);
680712

681713
return writer;
682714
}
683715

684-
// Set the default CRAM writer preservation parameters
685-
private void setCRAMWriterDefaults(final CRAMFileWriter writer) {
686-
//TODO: set encoding params
687-
//writer.setEncodingParams(new CRAMEncodingStrategy());
688-
}
689-
690716
@Override
691717
public String toString() {
692718
return "SAMFileWriterFactory [createIndex=" + createIndex + ", createMd5File=" + createMd5File + ", useAsyncIo="

0 commit comments

Comments
 (0)