From 78096d547e034aaa82a5e3129bd4860110d0f650 Mon Sep 17 00:00:00 2001 From: delocalizer Date: Mon, 21 Aug 2023 15:28:09 +1000 Subject: [PATCH 1/5] minimal changes to address #915. Existing tests pass --- src/main/java/picard/sam/FastqToSam.java | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/picard/sam/FastqToSam.java b/src/main/java/picard/sam/FastqToSam.java index 4d437cbec9..014dbb3fa6 100644 --- a/src/main/java/picard/sam/FastqToSam.java +++ b/src/main/java/picard/sam/FastqToSam.java @@ -328,11 +328,14 @@ protected int doWork() { final SAMFileWriter writer = new SAMFileWriterFactory().makeWriter(header, false, OUTPUT, REFERENCE_SEQUENCE); final FastqReader reader1 = fileToFastqReader(FASTQ.toPath()); + final boolean pipedInput = !Files.isRegularFile(FASTQ.toPath()); if (reader1.hasNext()) { - // Set the quality format - QUALITY_FORMAT = FastqToSam.determineQualityFormat(reader1, - (FASTQ2 == null) ? null : fileToFastqReader(FASTQ2.toPath()), - QUALITY_FORMAT); + if (!pipedInput) { + // Set the quality format + QUALITY_FORMAT = FastqToSam.determineQualityFormat(reader1, + (FASTQ2 == null) ? null : fileToFastqReader(FASTQ2.toPath()), + QUALITY_FORMAT); + } } else { if (ALLOW_EMPTY_FASTQ) { LOG.warn("Input FASTQ is empty, will write out SAM/BAM file with no reads."); @@ -360,9 +363,14 @@ protected int doWork() { } } else { - readers1.add(fileToFastqReader(FASTQ.toPath())); - if (FASTQ2 != null) { - readers2.add(fileToFastqReader(FASTQ2.toPath())); + if (pipedInput) { + // use the already opened reader if input is STDIN or a named pipe + readers1.add(reader1); + } else { + readers1.add(fileToFastqReader(FASTQ.toPath())); + if (FASTQ2 != null) { + readers2.add(fileToFastqReader(FASTQ2.toPath())); + } } } @@ -582,6 +590,7 @@ private String error(final FastqReader freader, final String str) { protected String[] customCommandLineValidation() { if (MIN_Q < 0) return new String[]{"MIN_Q must be >= 0"}; if (MAX_Q > SAMUtils.MAX_PHRED_SCORE) return new String[]{"MAX_Q must be <= " + SAMUtils.MAX_PHRED_SCORE}; + if (!Files.isRegularFile(FASTQ.toPath()) && QUALITY_FORMAT == null) return new String[]{"QUALITY_FORMAT must be specified when input is not a regular file"}; return null; } } From ee2d3e098cf1eebff45cd1a4fe5133b7d474dbf9 Mon Sep 17 00:00:00 2001 From: delocalizer Date: Mon, 21 Aug 2023 17:11:01 +1000 Subject: [PATCH 2/5] unit tests for FastqToSam stdin input happy & sad paths --- src/test/java/picard/sam/FastqToSamTest.java | 65 ++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/test/java/picard/sam/FastqToSamTest.java b/src/test/java/picard/sam/FastqToSamTest.java index 504aae115a..734cb7395c 100644 --- a/src/test/java/picard/sam/FastqToSamTest.java +++ b/src/test/java/picard/sam/FastqToSamTest.java @@ -36,8 +36,10 @@ import picard.cmdline.CommandLineProgramTest; import picard.PicardException; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.PrintStream; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -47,6 +49,8 @@ */ public class FastqToSamTest extends CommandLineProgramTest { private static final File TEST_DATA_DIR = new File("testdata/picard/sam/fastq2bam"); + private static final String CLASSPATH = "\"" + System.getProperty("java.class.path") + "\" "; + public String getCommandLineProgramName() { return FastqToSam.class.getSimpleName(); @@ -177,6 +181,67 @@ public void testEmptyFastqAllowed() throws IOException { convertFile(emptyFastq, null, FastqQualityFormat.Illumina, false, false, true); } + @Test + public void testStreamInputWithoutQuality() throws IOException { + ByteArrayOutputStream stderrStream = new ByteArrayOutputStream(); + PrintStream newStderr = new PrintStream(stderrStream); + PrintStream oldStderr = System.err; + System.setErr(newStderr); + + final File tmpFile = File.createTempFile("empty", ".sam"); + + final String[] args = { + "FASTQ=/dev/stdin", + "SAMPLE_NAME=sample001", + "OUTPUT=" + tmpFile + }; + + Assert.assertEquals(runPicardCommandLine(args), 1); + System.setErr(oldStderr); + Assert.assertTrue(stderrStream.toString().endsWith("QUALITY_FORMAT must be specified when input is not a regular file\n")); + } + + @Test + public void testStreamInput() throws IOException { + final File output = newTempSamFile("stdin"); + String[] command = { + "/bin/bash", + "-c", + "echo -ne '" + + "@ERR194147.10008417/1\\n" + + "ATTTAATTAAGAAAATGTAAACTAAATGACAGTAGACAGACAAGTATGCCTTTGC\\n" + + "+\\n" + + "???????????????????????????????????????????????????????\\n'" + + "|" + + "java -classpath " + + CLASSPATH + + "picard.cmdline.PicardCommandLine " + + "FastqToSam " + + "-FASTQ /dev/stdin " + + "-QUALITY_FORMAT Standard " + + "-SAMPLE_NAME sample001 " + + "-OUTPUT " + output + }; + + try { + ProcessBuilder processBuilder = new ProcessBuilder(command); + processBuilder.inheritIO(); + Process process = processBuilder.start(); + Assert.assertEquals(process.waitFor(), 0); + } catch (Exception e) { + Assert.fail("Failed to pipe data from stdin to FastqToSam", e); + } + final SamReader samReader = SamReaderFactory.makeDefault().open(output); + final SAMRecordIterator iterator = samReader.iterator(); + int actualCount = 0; + while (iterator.hasNext()) { + iterator.next(); + actualCount++; + } + samReader.close(); + Assert.assertEquals(actualCount, 1); + } + private File convertFile(final String filename, final FastqQualityFormat version) throws IOException { return convertFile(filename, null, version); } From 59ca6ea91e06858ce6b470519f87b5e521c327a0 Mon Sep 17 00:00:00 2001 From: delocalizer Date: Mon, 21 Aug 2023 17:25:48 +1000 Subject: [PATCH 3/5] update docs --- src/main/java/picard/sam/FastqToSam.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/picard/sam/FastqToSam.java b/src/main/java/picard/sam/FastqToSam.java index 014dbb3fa6..adb8954fef 100644 --- a/src/main/java/picard/sam/FastqToSam.java +++ b/src/main/java/picard/sam/FastqToSam.java @@ -82,7 +82,7 @@ *

*

* By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly - * using the QUALITY_FORMAT argument. + * using the QUALITY_FORMAT argument, and must do so if input is not from a regular file. *

*

Output

* A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name. @@ -132,7 +132,7 @@ public class FastqToSam extends CommandLineProgram { "

Alternatively, for larger inputs you can provide a collection of FASTQ files indexed by their name " + "(see USE_SEQUENCIAL_FASTQ for details below).

" + "

By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly " + - "using the QUALITY_FORMAT argument.

" + + "using the QUALITY_FORMAT argument, and must do so if input is not from a regular file.

" + "

Output

" + "

A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name.

" + "

Usage examples

" + @@ -176,7 +176,8 @@ public class FastqToSam extends CommandLineProgram { @Argument(shortName="V", doc="A value describing how the quality values are encoded in the input FASTQ file. " + "Either Solexa (phred scaling + 66), Illumina (phred scaling + 64) or Standard (phred scaling + 33). " + - "If this value is not specified, the quality format will be detected automatically.", optional = true) + "If input is from a regular file and this value is not specified, the quality format will be detected automatically. " + + "If input is from STDIN or a named pipe, this value is required.", optional = true) public FastqQualityFormat QUALITY_FORMAT; @Argument(doc="Output BAM/SAM/CRAM file. ", shortName=StandardOptionDefinitions.OUTPUT_SHORT_NAME) From 6451e7eef57202bf94d86141d1419e0c85c57b07 Mon Sep 17 00:00:00 2001 From: delocalizer Date: Tue, 22 Aug 2023 14:28:51 +1000 Subject: [PATCH 4/5] requested changes --- src/main/java/picard/nio/PicardHtsPath.java | 17 ++++++ src/main/java/picard/sam/FastqToSam.java | 55 +++++++++++--------- src/test/java/picard/sam/FastqToSamTest.java | 51 +++++++++--------- 3 files changed, 72 insertions(+), 51 deletions(-) diff --git a/src/main/java/picard/nio/PicardHtsPath.java b/src/main/java/picard/nio/PicardHtsPath.java index f9ca70dd93..f82a788dc9 100644 --- a/src/main/java/picard/nio/PicardHtsPath.java +++ b/src/main/java/picard/nio/PicardHtsPath.java @@ -30,7 +30,9 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -83,6 +85,21 @@ public static List fromPaths(Collection paths) { return paths.stream().map(PicardHtsPath::new).collect(Collectors.toList()); } + /** + * Test if the URI of this object is something other than a regular file, directory, or + * symbolic link. + * + * @return {@code true} if it's a device, socket or named pipe + * @throws RuntimeException if an I/O error occurs when creating the file system + */ + public boolean isOther() { + try { + return Files.readAttributes(IOUtil.getPath(super.getURIString()), BasicFileAttributes.class).isOther(); + } catch (IOException e) { + throw new RuntimeIOException(e); + } + } + /** * Resolve the URI of this object to a {@link Path} object. * diff --git a/src/main/java/picard/sam/FastqToSam.java b/src/main/java/picard/sam/FastqToSam.java index adb8954fef..d9807252d0 100644 --- a/src/main/java/picard/sam/FastqToSam.java +++ b/src/main/java/picard/sam/FastqToSam.java @@ -23,6 +23,17 @@ */ package picard.sam; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.broadinstitute.barclay.argparser.Argument; +import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; +import org.broadinstitute.barclay.help.DocumentedFeature; + import htsjdk.samtools.ReservedTagConstants; import htsjdk.samtools.SAMException; import htsjdk.samtools.SAMFileHeader; @@ -44,24 +55,12 @@ import htsjdk.samtools.util.SequenceUtil; import htsjdk.samtools.util.SolexaQualityConverter; import htsjdk.samtools.util.StringUtil; -import org.broadinstitute.barclay.argparser.Argument; -import org.broadinstitute.barclay.argparser.CommandLineProgramProperties; -import org.broadinstitute.barclay.help.DocumentedFeature; import picard.PicardException; import picard.cmdline.CommandLineProgram; import picard.cmdline.StandardOptionDefinitions; import picard.cmdline.programgroups.ReadDataManipulationProgramGroup; import picard.nio.PicardHtsPath; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - /** * Converts a FASTQ file to an unaligned BAM or SAM file. *

@@ -78,11 +77,14 @@ * These files might be in gzip compressed format (when file name is ending with ".gz"). *

*

- * Alternatively, for larger inputs you can provide a collection of FASTQ files indexed by their name (see USE_SEQUENCIAL_FASTQ for details below). + * Alternatively, for larger inputs you can provide a collection of FASTQ files indexed by their name (see USE_SEQUENTIAL_FASTQ for details below). *

*

* By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly - * using the QUALITY_FORMAT argument, and must do so if input is not from a regular file. + * using the QUALITY_FORMAT argument, and must do so if FASTQ is not a regular file (e.g. stdin). + *

+ *

+ * FASTQ2 input is not supported when FASTQ is not a regular file: you may consider using upstream tools to merge multiple inputs into a single input stream. *

*

Output

* A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name. @@ -130,9 +132,11 @@ public class FastqToSam extends CommandLineProgram { "

One FASTQ file name for single-end or two for pair-end sequencing input data. " + "These files might be in gzip compressed format (when file name is ending with \".gz\").

" + "

Alternatively, for larger inputs you can provide a collection of FASTQ files indexed by their name " + - "(see USE_SEQUENCIAL_FASTQ for details below).

" + + "(see USE_SEQUENTIAL_FASTQ for details below).

" + "

By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly " + - "using the QUALITY_FORMAT argument, and must do so if input is not from a regular file.

" + + "using the QUALITY_FORMAT argument, and must do so if FASTQ is not a regular file (e.g. stdin).

" + + "

FASTQ2 input is not supported when FASTQ is not a regular file: you may consider using " + + "upstream tools to merge multiple inputs into a single input stream.

" + "

Output

" + "

A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name.

" + "

Usage examples

" + @@ -240,6 +244,8 @@ public class FastqToSam extends CommandLineProgram { private static final SolexaQualityConverter solexaQualityConverter = SolexaQualityConverter.getSingleton(); + private Boolean regularFileInput; + /** * Looks at fastq input(s) and attempts to determine the proper quality format * @@ -329,9 +335,8 @@ protected int doWork() { final SAMFileWriter writer = new SAMFileWriterFactory().makeWriter(header, false, OUTPUT, REFERENCE_SEQUENCE); final FastqReader reader1 = fileToFastqReader(FASTQ.toPath()); - final boolean pipedInput = !Files.isRegularFile(FASTQ.toPath()); if (reader1.hasNext()) { - if (!pipedInput) { + if (regularFileInput) { // Set the quality format QUALITY_FORMAT = FastqToSam.determineQualityFormat(reader1, (FASTQ2 == null) ? null : fileToFastqReader(FASTQ2.toPath()), @@ -364,7 +369,7 @@ protected int doWork() { } } else { - if (pipedInput) { + if (!regularFileInput) { // use the already opened reader if input is STDIN or a named pipe readers1.add(reader1); } else { @@ -414,7 +419,7 @@ protected int doUnpaired(final FastqReader freader, final SAMFileWriter writer) final ProgressLogger progress = new ProgressLogger(LOG); for ( ; freader.hasNext() ; readCount++) { final FastqRecord frec = freader.next(); - final SAMRecord srec = createSamRecord(writer.getFileHeader(), SequenceUtil.getSamReadNameFromFastqHeader(frec.getReadHeader()) , frec, false) ; + final SAMRecord srec = createSamRecord(writer.getFileHeader(), SequenceUtil.getSamReadNameFromFastqHeader(frec.getReadName()) , frec, false) ; srec.setReadPairedFlag(false); writer.addAlignment(srec); progress.record(srec); @@ -431,8 +436,8 @@ protected int doPaired(final FastqReader freader1, final FastqReader freader2, f final FastqRecord frec1 = freader1.next(); final FastqRecord frec2 = freader2.next(); - final String frec1Name = SequenceUtil.getSamReadNameFromFastqHeader(frec1.getReadHeader()); - final String frec2Name = SequenceUtil.getSamReadNameFromFastqHeader(frec2.getReadHeader()); + final String frec1Name = SequenceUtil.getSamReadNameFromFastqHeader(frec1.getReadName()); + final String frec2Name = SequenceUtil.getSamReadNameFromFastqHeader(frec2.getReadName()); final String baseName = getBaseName(frec1Name, frec2Name, freader1, freader2); final SAMRecord srec1 = createSamRecord(writer.getFileHeader(), baseName, frec1, true) ; @@ -471,7 +476,7 @@ private SAMRecord createSamRecord(final SAMFileHeader header, final String baseN final int uQual = qual & 0xff; if (uQual < MIN_Q || uQual > MAX_Q) { throw new PicardException("Base quality " + uQual + " is not in the range " + MIN_Q + ".." + - MAX_Q + " for read " + frec.getReadHeader()); + MAX_Q + " for read " + frec.getReadName()); } } srec.setBaseQualities(quals); @@ -591,7 +596,9 @@ private String error(final FastqReader freader, final String str) { protected String[] customCommandLineValidation() { if (MIN_Q < 0) return new String[]{"MIN_Q must be >= 0"}; if (MAX_Q > SAMUtils.MAX_PHRED_SCORE) return new String[]{"MAX_Q must be <= " + SAMUtils.MAX_PHRED_SCORE}; - if (!Files.isRegularFile(FASTQ.toPath()) && QUALITY_FORMAT == null) return new String[]{"QUALITY_FORMAT must be specified when input is not a regular file"}; + regularFileInput = !FASTQ.isOther(); + if (!regularFileInput && QUALITY_FORMAT == null) return new String[]{"QUALITY_FORMAT must be specified when FASTQ is not a regular file"}; + if (!regularFileInput && FASTQ2 != null) return new String[]{"FASTQ2 input is not supported when FASTQ is not a regular file"}; return null; } } diff --git a/src/test/java/picard/sam/FastqToSamTest.java b/src/test/java/picard/sam/FastqToSamTest.java index 734cb7395c..6a41d99eb6 100644 --- a/src/test/java/picard/sam/FastqToSamTest.java +++ b/src/test/java/picard/sam/FastqToSamTest.java @@ -186,33 +186,34 @@ public void testStreamInputWithoutQuality() throws IOException { ByteArrayOutputStream stderrStream = new ByteArrayOutputStream(); PrintStream newStderr = new PrintStream(stderrStream); PrintStream oldStderr = System.err; - System.setErr(newStderr); - - final File tmpFile = File.createTempFile("empty", ".sam"); - - final String[] args = { - "FASTQ=/dev/stdin", - "SAMPLE_NAME=sample001", - "OUTPUT=" + tmpFile - }; - - Assert.assertEquals(runPicardCommandLine(args), 1); - System.setErr(oldStderr); - Assert.assertTrue(stderrStream.toString().endsWith("QUALITY_FORMAT must be specified when input is not a regular file\n")); + try { + System.setErr(newStderr); + final File tmpFile = File.createTempFile("empty", ".sam"); + final String[] args = { + "FASTQ=/dev/stdin", + "SAMPLE_NAME=sample001", + "OUTPUT=" + tmpFile + }; + Assert.assertEquals(runPicardCommandLine(args), 1); + } finally { + System.setErr(oldStderr); + } + Assert.assertTrue(stderrStream.toString().endsWith("QUALITY_FORMAT must be specified when FASTQ is not a regular file\n")); } @Test public void testStreamInput() throws IOException { final File output = newTempSamFile("stdin"); + String fastq = """ + @ERR194147.10008417/1 + ATTTAATTAAGAAAATGTAAACTAAATGACAGTAGACAGACAAGTATGCCTTTGC + + + ??????????????????????????????????????????????????????? + """; String[] command = { "/bin/bash", "-c", - "echo -ne '" + - "@ERR194147.10008417/1\\n" + - "ATTTAATTAAGAAAATGTAAACTAAATGACAGTAGACAGACAAGTATGCCTTTGC\\n" + - "+\\n" + - "???????????????????????????????????????????????????????\\n'" + - "|" + + "echo -n '" + fastq + "'|" + "java -classpath " + CLASSPATH + "picard.cmdline.PicardCommandLine " + @@ -231,15 +232,11 @@ public void testStreamInput() throws IOException { } catch (Exception e) { Assert.fail("Failed to pipe data from stdin to FastqToSam", e); } - final SamReader samReader = SamReaderFactory.makeDefault().open(output); - final SAMRecordIterator iterator = samReader.iterator(); - int actualCount = 0; - while (iterator.hasNext()) { - iterator.next(); - actualCount++; + + try(final SamReader samReader = SamReaderFactory.makeDefault().open(output)) { + long actualCount = samReader.iterator().stream().count(); + Assert.assertEquals(actualCount, 1); } - samReader.close(); - Assert.assertEquals(actualCount, 1); } private File convertFile(final String filename, final FastqQualityFormat version) throws IOException { From 94e41f3d9b21ce2e611ded9c85eeadbebb25e989 Mon Sep 17 00:00:00 2001 From: delocalizer Date: Thu, 24 Aug 2023 19:23:24 +1000 Subject: [PATCH 5/5] requested changes --- src/main/java/picard/nio/PicardHtsPath.java | 42 +++++++++++--------- src/main/java/picard/sam/FastqToSam.java | 32 ++++++--------- src/test/java/picard/sam/FastqToSamTest.java | 2 +- 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/main/java/picard/nio/PicardHtsPath.java b/src/main/java/picard/nio/PicardHtsPath.java index f82a788dc9..3856244f5b 100644 --- a/src/main/java/picard/nio/PicardHtsPath.java +++ b/src/main/java/picard/nio/PicardHtsPath.java @@ -24,10 +24,6 @@ package picard.nio; -import htsjdk.io.HtsPath; -import htsjdk.samtools.util.IOUtil; -import htsjdk.samtools.util.RuntimeIOException; - import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -38,6 +34,11 @@ import java.util.Objects; import java.util.stream.Collectors; +import htsjdk.io.HtsPath; +import htsjdk.io.IOPath; +import htsjdk.samtools.util.IOUtil; +import htsjdk.samtools.util.RuntimeIOException; + /** * A Subclass of {@link HtsPath} with conversion to {@link Path} making use of {@link IOUtil} */ @@ -85,21 +86,6 @@ public static List fromPaths(Collection paths) { return paths.stream().map(PicardHtsPath::new).collect(Collectors.toList()); } - /** - * Test if the URI of this object is something other than a regular file, directory, or - * symbolic link. - * - * @return {@code true} if it's a device, socket or named pipe - * @throws RuntimeException if an I/O error occurs when creating the file system - */ - public boolean isOther() { - try { - return Files.readAttributes(IOUtil.getPath(super.getURIString()), BasicFileAttributes.class).isOther(); - } catch (IOException e) { - throw new RuntimeIOException(e); - } - } - /** * Resolve the URI of this object to a {@link Path} object. * @@ -125,6 +111,24 @@ public static PicardHtsPath fromPath(final Path path){ return new PicardHtsPath(new HtsPath(path.toUri().toString())); } + /** + * Test if {@code ioPath} is something other than a regular file, directory, or symbolic link. + * + * @return {@code true} if it's a device, named pipe, htsget API URL, etc. + * @throws RuntimeException if an I/O error occurs when creating the file system + */ + public static boolean isOther(final IOPath ioPath) { + if(ioPath.isPath()) { + try { + return Files.readAttributes(ioPath.toPath(), BasicFileAttributes.class).isOther(); + } catch (IOException e) { + throw new RuntimeIOException(e); + } + } else { + return true; + } + } + /** * Create a {@link List} from {@link PicardHtsPath}s * @param picardHtsPaths may NOT be null diff --git a/src/main/java/picard/sam/FastqToSam.java b/src/main/java/picard/sam/FastqToSam.java index d9807252d0..063ffca78a 100644 --- a/src/main/java/picard/sam/FastqToSam.java +++ b/src/main/java/picard/sam/FastqToSam.java @@ -81,10 +81,7 @@ *

*

* By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly - * using the QUALITY_FORMAT argument, and must do so if FASTQ is not a regular file (e.g. stdin). - *

- *

- * FASTQ2 input is not supported when FASTQ is not a regular file: you may consider using upstream tools to merge multiple inputs into a single input stream. + * using the QUALITY_FORMAT argument, and must do so if inputs are not a regular file (e.g. stdin). *

*

Output

* A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name. @@ -134,9 +131,7 @@ public class FastqToSam extends CommandLineProgram { "

Alternatively, for larger inputs you can provide a collection of FASTQ files indexed by their name " + "(see USE_SEQUENTIAL_FASTQ for details below).

" + "

By default, this tool will try to guess the base quality score encoding. However you can indicate it explicitly " + - "using the QUALITY_FORMAT argument, and must do so if FASTQ is not a regular file (e.g. stdin).

" + - "

FASTQ2 input is not supported when FASTQ is not a regular file: you may consider using " + - "upstream tools to merge multiple inputs into a single input stream.

" + + "using the QUALITY_FORMAT argument, and must do so if inputs are not a regular file (e.g. stdin).

" + "

Output

" + "

A single unaligned BAM or SAM file. By default, the records are sorted by query (read) name.

" + "

Usage examples

" + @@ -181,7 +176,7 @@ public class FastqToSam extends CommandLineProgram { @Argument(shortName="V", doc="A value describing how the quality values are encoded in the input FASTQ file. " + "Either Solexa (phred scaling + 66), Illumina (phred scaling + 64) or Standard (phred scaling + 33). " + "If input is from a regular file and this value is not specified, the quality format will be detected automatically. " + - "If input is from STDIN or a named pipe, this value is required.", optional = true) + "If input is not from a regular file, this value is required.", optional = true) public FastqQualityFormat QUALITY_FORMAT; @Argument(doc="Output BAM/SAM/CRAM file. ", shortName=StandardOptionDefinitions.OUTPUT_SHORT_NAME) @@ -244,6 +239,7 @@ public class FastqToSam extends CommandLineProgram { private static final SolexaQualityConverter solexaQualityConverter = SolexaQualityConverter.getSingleton(); + // tested for and set in customCommandLineValidation private Boolean regularFileInput; /** @@ -369,14 +365,10 @@ protected int doWork() { } } else { - if (!regularFileInput) { - // use the already opened reader if input is STDIN or a named pipe - readers1.add(reader1); - } else { - readers1.add(fileToFastqReader(FASTQ.toPath())); - if (FASTQ2 != null) { - readers2.add(fileToFastqReader(FASTQ2.toPath())); - } + // use the already opened reader1 if input is STDIN or a named pipe + readers1.add(regularFileInput ? fileToFastqReader(FASTQ.toPath()) : reader1); + if (FASTQ2 != null) { + readers2.add(fileToFastqReader(FASTQ2.toPath())); } } @@ -596,9 +588,11 @@ private String error(final FastqReader freader, final String str) { protected String[] customCommandLineValidation() { if (MIN_Q < 0) return new String[]{"MIN_Q must be >= 0"}; if (MAX_Q > SAMUtils.MAX_PHRED_SCORE) return new String[]{"MAX_Q must be <= " + SAMUtils.MAX_PHRED_SCORE}; - regularFileInput = !FASTQ.isOther(); - if (!regularFileInput && QUALITY_FORMAT == null) return new String[]{"QUALITY_FORMAT must be specified when FASTQ is not a regular file"}; - if (!regularFileInput && FASTQ2 != null) return new String[]{"FASTQ2 input is not supported when FASTQ is not a regular file"}; + regularFileInput = !PicardHtsPath.isOther(FASTQ) && (FASTQ2 == null || !PicardHtsPath.isOther(FASTQ2)); + if (QUALITY_FORMAT == null && !regularFileInput) { + return new String[]{"QUALITY_FORMAT must be specified when either of FASTQ or FASTQ2 is not a regular file"}; + } return null; } + } diff --git a/src/test/java/picard/sam/FastqToSamTest.java b/src/test/java/picard/sam/FastqToSamTest.java index 6a41d99eb6..678d8f2bd2 100644 --- a/src/test/java/picard/sam/FastqToSamTest.java +++ b/src/test/java/picard/sam/FastqToSamTest.java @@ -198,7 +198,7 @@ public void testStreamInputWithoutQuality() throws IOException { } finally { System.setErr(oldStderr); } - Assert.assertTrue(stderrStream.toString().endsWith("QUALITY_FORMAT must be specified when FASTQ is not a regular file\n")); + Assert.assertTrue(stderrStream.toString().endsWith("QUALITY_FORMAT must be specified when either of FASTQ or FASTQ2 is not a regular file\n")); } @Test