Commit 72f71463 authored by Charles Girardot's avatar Charles Girardot

added all functionality, needs in depth tsting now

parent 087183a0
#jars found in this folder are artifact that are not found in maven central, you can then puch them in your local maven repo with the following commands:
#ADAPT fpath to YOUR Je/lib
LIBPATH="/Users/girardot/Work/eclipse_ws/Je/lib/"
#ADAPT path to YOUR Je/lib
LIBPATH="/Users/girardot/git/Je/lib/"
cd ~/.m2
mvn install:install-file -DgroupId=net.sf -DartifactId=htsjdk -Dversion=1.140custom -Dfile=$LIBPATH/custom-picard/htsjdk-1.140.jar -Dpackaging=jar -DgeneratePom=true
mvn install:install-file -DgroupId=net.sf -DartifactId=picard -Dversion=1.140custom -Dfile=$LIBPATH/custom-picard/picard.jar -Dpackaging=jar -DgeneratePom=true
mvn install:install-file -DgroupId=org.broadinstitute -DartifactId=picard -Dversion=2.9.4 -Dfile=$LIBPATH/picard_2.9.4.jar -Dpackaging=jar -DgeneratePom=true
# Uncomment to ADD GBCS artifacts if needed (ie if you don t have access to these repos)
# IF you are at embl, you rather want to checkout the relevant projects and build them locally
......
......@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>Je</groupId>
<artifactId>Je</artifactId>
<version>1.1</version>
<version>2.0.beta</version>
<name>Je</name>
<description>Je provides command line utilities to deal with barcoded FASTQ files with or without Unique Molecular Index (UMI)</description>
......@@ -233,34 +233,9 @@
<dependency>
<groupId>org.embl.cg.utilitytools</groupId>
<artifactId>ut_utils</artifactId>
<version>1.0</version>
</dependency>
<!-- <dependency> -->
<!-- <groupId>net.sf</groupId> -->
<!-- <artifactId>picard</artifactId> -->
<!-- <version>1.140</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>net.sf</groupId> -->
<!-- <artifactId>htsjdk</artifactId> -->
<!-- <version>1.140</version> -->
<!-- </dependency> -->
<dependency>
<groupId>net.sf</groupId>
<artifactId>picard</artifactId>
<version>1.140custom</version>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>net.sf</groupId>
<artifactId>htsjdk</artifactId>
<version>1.140custom</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
......@@ -291,6 +266,11 @@
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.broadinstitute</groupId>
<artifactId>picard</artifactId>
<version>2.9.4</version>
</dependency>
</dependencies>
......
......@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.embl.gbcs.je.jemultiplexer;
package org.embl.gbcs.je;
import java.io.IOException;
import java.io.InputStream;
......
......@@ -21,13 +21,15 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.embl.gbcs.je.jemultiplexer;
package org.embl.gbcs.je;
/** Utility class to hang onto data about the best match for a given barcode */
public class BarcodeMatch {
/**
* indicates if a barcode match has been found, in which case 'barcode' is not null
* indicates if this barcode match fullfils the thresholds for barcode matching
*/
public boolean matched;
......@@ -35,6 +37,12 @@ public class BarcodeMatch {
* sequence of the matched barcode
*/
public String barcode;
/**
* sequence extracted from read
*/
public String readSequence;
/**
* number of mismatches with 'barcode'
......@@ -48,8 +56,9 @@ public class BarcodeMatch {
public String toString(){
if(matched)
return "matched :"+ barcode+" [MM="+mismatches+", MMD="+mismatchesToSecondBest+"]";
return "no match";
return "Match for "+readSequence+ " read sequence : barcode "+ barcode+" identified with [MM="+mismatches+", MMD="+mismatchesToSecondBest+"]";
else
return "No Match for "+readSequence+ " read sequence (best barcode is "+ barcode+" identified with [MM="+mismatches+", MMD="+mismatchesToSecondBest+"])";
}
}
This diff is collapsed.
......@@ -26,7 +26,9 @@ package org.embl.gbcs.je;
import java.util.Set;
import java.util.TreeSet;
import org.embl.cg.utilitytools.utils.ExceptionUtil;
import org.embl.cg.utilitytools.utils.StringUtil;
import org.embl.gbcs.je.demultiplexer.Jedemultiplex;
import org.embl.gbcs.je.jeclipper.Jeclipper;
import org.embl.gbcs.je.jedropseq.Jedropseq;
import org.embl.gbcs.je.jeduplicates.MarkDuplicatesWithMolecularCode;
......@@ -48,6 +50,7 @@ public class Je {
private static Logger log = LoggerFactory.getLogger(Je.class);
public static final String COMMAND_DEMULTIPLEX = "debarcode";
public static final String COMMAND_DROPSEQ = "dropseq";
public static final String COMMAND_CLIP = "clip";
public static final String COMMAND_DUPES = "markdupes";
......@@ -62,6 +65,7 @@ public class Je {
ALLOWED_COMMANDS.add(COMMAND_MULTIPLEX);
ALLOWED_COMMANDS.add(COMMAND_MULTIPLEX_ILLUMINA);
ALLOWED_COMMANDS.add(COMMAND_DROPSEQ);
ALLOWED_COMMANDS.add(COMMAND_DEMULTIPLEX);
}
......@@ -90,49 +94,57 @@ public class Je {
System.exit(0);
}
else if(!ALLOWED_COMMANDS.contains(option.toLowerCase())){
System.err.println("Unknown command name : "+option);
System.err.println(getUsage());
System.err.println("Unknown command name : "+option+" ; please check help with -h");
//System.err.println(getUsage());
System.exit(1); //error
}
/*
* looks good , we delegate to proper implementation
*/
String [] argv = {"-h"}; // init to get help
if(args.length > 1){
argv = StringUtil.subArray(args, 1, args.length-1);
}
if(option.equalsIgnoreCase(COMMAND_CLIP)){
new Jeclipper().instanceMainWithExit(argv);
}
else if(option.equalsIgnoreCase(COMMAND_MULTIPLEX)){
new Jemultiplexer().instanceMainWithExit(argv);
}
else if(option.equalsIgnoreCase(COMMAND_MULTIPLEX_ILLUMINA)){
new JemultiplexerIllumina().instanceMainWithExit(argv);
}
else if(option.equalsIgnoreCase(COMMAND_DUPES)){
new MarkDuplicatesWithMolecularCode().instanceMainWithExit(argv);
}
else if(option.equalsIgnoreCase(COMMAND_DROPSEQ)){
new Jedropseq().instanceMainWithExit(argv);
}
else{
System.err.println(
"FATAL : We just reached a supposedly unreachable part of the code. Please report this bug to Je developpers indicating the options you used i.e. : \n "+
StringUtil.mergeArray(args, " ")
);
try{
String [] argv = {"-h"}; // init to get help
if(args.length > 1){
argv = StringUtil.subArray(args, 1, args.length-1);
}
if(option.equalsIgnoreCase(COMMAND_CLIP)){
new Jeclipper().instanceMainWithExit(argv);
}
else if(option.equalsIgnoreCase(COMMAND_DEMULTIPLEX)){
new Jedemultiplex().instanceMainWithExit(argv);
}
else if(option.equalsIgnoreCase(COMMAND_MULTIPLEX)){
new Jemultiplexer().instanceMainWithExit(argv);
}
else if(option.equalsIgnoreCase(COMMAND_MULTIPLEX_ILLUMINA)){
new JemultiplexerIllumina().instanceMainWithExit(argv);
}
else if(option.equalsIgnoreCase(COMMAND_DUPES)){
new MarkDuplicatesWithMolecularCode().instanceMainWithExit(argv);
}
else if(option.equalsIgnoreCase(COMMAND_DROPSEQ)){
new Jedropseq().instanceMainWithExit(argv);
}
else{
System.err.println(
"FATAL : We just reached a supposedly unreachable part of the code. Please report this bug to Je developpers indicating the options you used i.e. : \n "+
StringUtil.mergeArray(args, " ")
);
System.exit(1); //error
}
} catch(Exception e){
log.error(ExceptionUtil.getStackTrace(e));
System.exit(1); //error
}
}
protected static String getUsage(){
return "Usage: je <command> [options] \n\n"+
"with command in : \n"
+"\t "+COMMAND_CLIP+" \t\t clips molecular barcodes from fastq sequence and places them in read name headers for further use in 'dupes' module\n"
+"\t "+COMMAND_MULTIPLEX+" \t\t demultiplex fastq file(s), with optional handling of molecular barcodes for further use in 'dupes' module\n"
+"\t "+COMMAND_MULTIPLEX_ILLUMINA+" \t demultiplex fastq file(s) using Illumina Index files, with optional handling of molecular barcodes for further use in 'dupes' module\n"
+"\t "+COMMAND_DEMULTIPLEX+" \t\t demultiplexes fastq file(s), with optional handling of molecular barcodes for further use in 'dupes' module\n"
+"\t "+COMMAND_MULTIPLEX+" \t\t demultiplexes fastq file(s) with Je 1.x implementation, with optional handling of molecular barcodes for further use in 'dupes' module\n"
+"\t "+COMMAND_MULTIPLEX_ILLUMINA+" \t demultiplexes fastq file(s) using Illumina Index files with Je 1.x implementation, with optional handling of molecular barcodes for further use in 'dupes' module\n"
+"\t "+COMMAND_DUPES+" \t\t removes read duplicates based on molecular barcodes found in read name headers (as produced by clip or plex)\n"
+"\t "+COMMAND_DROPSEQ+" \t\t clips cell barcode and UMI from read 1 and adds them to header of read 2. This command is for processing drop-seq results.\n"
+"\n"
......
......@@ -23,6 +23,9 @@
*/
package org.embl.gbcs.je;
import java.util.Set;
import java.util.TreeSet;
import com.developpez.adiguba.shell.ProcessConsumer;
public class JeUtils {
......@@ -61,6 +64,19 @@ public class JeUtils {
}
public static int barcodeSlotCount(ReadLayout[] readLayouts) {
return barcodeBlockUniqueIdSet(readLayouts).size();
}
public static Set<Integer> barcodeBlockUniqueIdSet(ReadLayout[] readLayouts) {
Set<Integer> allIds = new TreeSet<Integer>();
for (ReadLayout rl : readLayouts) {
allIds.addAll(rl.getBarcodeBlockUniqueIds());
}
return allIds;
}
......
......@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.embl.gbcs.je.jemultiplexer;
package org.embl.gbcs.je;
import htsjdk.samtools.Defaults;
import htsjdk.samtools.fastq.AsyncFastqWriter;
......@@ -37,7 +37,7 @@ import java.util.zip.GZIPOutputStream;
public class JemultiplexerFastqWriterFactory {
boolean useAsyncIo = Defaults.USE_ASYNC_IO;
boolean useAsyncIo = Defaults.USE_ASYNC_IO_WRITE_FOR_SAMTOOLS;
/** Sets whether or not to use async io (i.e. a dedicated thread per writer. */
......
......@@ -23,14 +23,14 @@
*/
package org.embl.gbcs.je;
public class ReadLayoutMalformedException extends Jexception {
public class LayoutMalformedException extends Jexception {
/**
*
*/
private static final long serialVersionUID = -4024288961353288534L;
public ReadLayoutMalformedException(String message, String layout) {
public LayoutMalformedException(String message, String layout) {
super(message+"\n Layout was : "+layout);
}
......
/*
* The MIT License
*
* Copyright (c) 2009 The Broad Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.embl.gbcs.je;
import htsjdk.samtools.SAMUtils;
import htsjdk.samtools.fastq.FastqRecord;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ReadLayoutConsumer {
private static Logger log = LoggerFactory.getLogger(ReadLayoutConsumer.class);
private static final byte BYTECODE_SAMPLE = 0;
private static final byte BYTECODE_BARCODE = 1;
private static final byte BYTECODE_UMI = 2;
ArrayList<Byte> slotCodes = new ArrayList<Byte>();
ArrayList<Integer> slotIdx = new ArrayList<Integer>();
ArrayList<Set<Integer>> layoutIndicesToUseForSlots = new ArrayList<Set<Integer>>();
ReadLayout [] readLayouts;
String outPutLayout;
/**
* @param outPutLayout in short format
* @param readLayouts all ordered layout (order is as the reads are read from files)
*/
public ReadLayoutConsumer(String outPutLayout, ReadLayout [] readLayouts){
this.outPutLayout = outPutLayout;
this.readLayouts = readLayouts;
Pattern sub = Pattern.compile("([BUS])(\\d+)");
Matcher subMatcher = sub.matcher("");
Pattern p = Pattern.compile("([BUS]\\d+)");
Matcher m = p.matcher(outPutLayout);
log.debug("ReadLayoutConsumer created with "+outPutLayout);
while(m.find()){
String _g = m.group();
log.debug(_g);
subMatcher.reset(_g);
subMatcher.matches();
String slotType = subMatcher.group(1);
Integer slotId = Integer.parseInt(subMatcher.group(2));
//save the slot code
byte bCode ;
if(slotType.equalsIgnoreCase("B"))
bCode = BYTECODE_BARCODE;
else if(slotType.equalsIgnoreCase("U"))
bCode = BYTECODE_UMI;
else
bCode = BYTECODE_SAMPLE;
log.debug("slot type "+slotType+" => "+bCode);
log.debug("slot id => "+slotId);
slotCodes.add(bCode);
//save the slot id
slotIdx.add(slotId);
//identify the ReadLayout to use to extract this slot
Set<Integer> allPossibleLayoutIdxToUse = new TreeSet<Integer>();
for (int i = 0; i < readLayouts.length; i++) {
ReadLayout rl = readLayouts[i];
boolean canBeUsed = false;
switch (bCode) {
case BYTECODE_BARCODE:
canBeUsed = rl.containsBarcode() && rl.getBarcodeBlockUniqueIds().contains(slotId);
break;
case BYTECODE_UMI:
canBeUsed = rl.containsUMI() && rl.getUMIBlockUniqueIds().contains(slotId);
break;
case BYTECODE_SAMPLE:
canBeUsed = rl.containsSampleSequence() && rl.getSampleBlockUniqueIds().contains(slotId);
break;
default:
throw new LayoutMalformedException("unknown block code in output layout :"+bCode, outPutLayout);
}
if(canBeUsed){
allPossibleLayoutIdxToUse.add(i);
log.debug("ReadLayout idx "+i+" can be used for lookup");
}
}
if(allPossibleLayoutIdxToUse.isEmpty())
throw new LayoutMalformedException("no read layout identified for block "+slotType+" with index "+slotIdx, this.outPutLayout);
layoutIndicesToUseForSlots.add(allPossibleLayoutIdxToUse);
}
}
/**
* Assemble a read name by concatenating the output layout to the original read name.
* Concatenation is made by inserting a readNameDelimitor between each added slot
*
*
* @param reads the reads in order matching that of the {@link ReadLayout} array used at construction
* @param useReadSequenceForBarcodes dictates what to write in the read header layouts of the {@link FastqWriterLayout} for each BARCODE.
* When false, the matched barcode is used. When true, the exact read sequence extracted from the barcode slot is written
* @param m a {@link SampleMatch} holding all the barcode matches
*
* @return
*/
public String assembleNewReadName(FastqRecord [] reads, boolean[] useReadSequenceForBarcodes, SampleMatch m){
String newname = reads[0].getReadName().split("\\s")[0];
if(newname.endsWith(FastqWriterLayout.readNameDelimitor))
newname = newname.substring(0, newname.length()-1);
log.debug("assembling read name with pattern "+this.outPutLayout);
for (int i = 0; i < slotCodes.size(); i++) {
byte slotTypeCode = slotCodes.get(i);
int slotIdx = this.slotIdx.get(i);
log.debug("assembling read name adding slot code "+slotTypeCode+" with id "+slotIdx);
/*
* when a slot can be obtained from different reads (e.g. redundant barcode), keep the one with best overall quality
*/
String subseq = null;
int bestQual = 0;
if(!useReadSequenceForBarcodes[slotIdx-1] && slotTypeCode == BYTECODE_BARCODE){
// we init the subseq with the matched barcode directly
subseq = m.getBarcodeMatches().get(slotIdx).barcode;
}else{
for(int rlIdx : layoutIndicesToUseForSlots.get(i)){
ReadLayout rl = readLayouts[rlIdx];
FastqRecord readForLayout = reads[rlIdx];
String _subseq = null;
String _subqual = null;
switch (slotTypeCode) {
case BYTECODE_BARCODE:
_subseq = rl.extractBarcode(readForLayout.getReadString(), slotIdx);
_subqual = rl.extractBarcode(readForLayout.getBaseQualityString(), slotIdx);
break;
case BYTECODE_UMI:
_subseq = rl.extractUMI(readForLayout.getReadString(), slotIdx);
_subqual = rl.extractUMI(readForLayout.getBaseQualityString(), slotIdx);
break;
default:
_subseq = rl.extractSample(readForLayout.getReadString(), slotIdx);
_subqual = rl.extractSample(readForLayout.getBaseQualityString(), slotIdx);
break;
}
int _qualsum = overallQuality( SAMUtils.fastqToPhred(_subqual) );
if(subseq == null || _qualsum > bestQual){
subseq = _subseq;
bestQual = _qualsum;
}
}
}
//concatenate to the growing name
newname += FastqWriterLayout.readNameDelimitor + subseq;
log.debug("header is now : "+newname);
}
return newname;
}
/**
* Assemble a FastqRecord using original read name and quality header.
* The read sequence (and associated quality string) are assembled according to the
* output layout given to this consumer
*
* @param reads the reads in order matching that of the {@link ReadLayout} array used at construction
* @return
*/
public FastqRecord assembleNewRead(FastqRecord [] reads){
String newseq = "";
String newqual = "";
log.debug("##### assembling read according to layout "+this.outPutLayout);
for (int i = 0; i < slotCodes.size(); i++) {
byte slotTypeCode = slotCodes.get(i);
int slotIdx = this.slotIdx.get(i);
log.debug("gettign info for slot code "+slotTypeCode+" with idx "+slotIdx);
/*
* when a slot can be obtained from different reads (e.g. redundant barcode), keep the one with best overall quality
*/
String subseq = null;
String subqual = null;
int bestQual = 0;
for(int rlIdx : layoutIndicesToUseForSlots.get(i)){
ReadLayout rl = readLayouts[rlIdx];
FastqRecord readForLayout = reads[rlIdx];
String _subseq, _subqual = null;
switch (slotTypeCode) {
case BYTECODE_BARCODE:
_subseq = rl.extractBarcode(readForLayout.getReadString(), slotIdx);
_subqual = rl.extractBarcode(readForLayout.getBaseQualityString(), slotIdx);
break;
case BYTECODE_UMI:
_subseq = rl.extractUMI(readForLayout.getReadString(), slotIdx);
_subqual = rl.extractUMI(readForLayout.getBaseQualityString(), slotIdx);
break;
default:
_subseq = rl.extractSample(readForLayout.getReadString(), slotIdx);
_subqual = rl.extractSample(readForLayout.getBaseQualityString(), slotIdx);
break;
}
int _qualsum = overallQuality( SAMUtils.fastqToPhred(_subqual) );
if(subseq == null || _qualsum > bestQual){
subseq = _subseq;
subqual = _subqual;
bestQual = _qualsum;
}
}
//concatenate to the growing newseq/newqual
newseq+=subseq;
newqual+=subqual;
}
//log.debug(newseq);
//log.debug(newqual);
//we borrow read name and qual header from first read, which must always be here
return new FastqRecord(reads[0].getReadName().split("\\s")[0], newseq, reads[0].getBaseQualityHeader(), newqual);
}
private int overallQuality(byte [] qualScores) {
int s = 0;
for (byte b : qualScores) {
s+= b;
}
return s;
}
}
/*
* The MIT License
*
* Copyright (c) 2009 The Broad Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.embl.gbcs.je;
import java.util.Map;
/** Utility class to hang onto data about the best match for a given barcode */
public class SampleMatch {
/**
* The identified sample name or null
*
*/
protected String sample;
/**
* The {@link BarcodeMatch} or null for each barcode slot
* A {@link BarcodeMatch} is always present if the sample has been identified
*/
protected Map<Integer, BarcodeMatch> barcodeMatches;
public SampleMatch(String sample, Map<Integer, BarcodeMatch> barcodeMatches){
this.sample= sample;
this.barcodeMatches = barcodeMatches;
}
/**
* @return the sample
*/
public String getSample() {
return sample;
}
/**
* @return the barcodeMatches
*/
public Map<Integer, BarcodeMatch> getBarcodeMatches() {
return barcodeMatches;
}
/**
* @param sample the sample to set
*/
public void setSample(String sample) {
this.sample = sample;
}
/**
* @param barcodeMatches the barcodeMatches to set
*/
public void setBarcodeMatches(Map<Integer, BarcodeMatch> barcodeMatches) {
this.barcodeMatches = barcodeMatches;
}
}
/*
* The MIT License
*
* Copyright (c) 2009 The Broad Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.embl.gbcs.je.demultiplexer;
import htsjdk.samtools.fastq.FastqReader;
import htsjdk.samtools.fastq.FastqRecord;
import java.io.File;