001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021package org.biojava.nbio.sequencing.io.fastq;
022
023import com.google.common.collect.ImmutableList;
024import com.google.common.collect.Lists;
025import org.biojava.nbio.core.exceptions.CompoundNotFoundException;
026import org.biojava.nbio.core.sequence.DNASequence;
027import org.biojava.nbio.core.sequence.compound.NucleotideCompound;
028import org.biojava.nbio.core.sequence.features.QualityFeature;
029import org.biojava.nbio.core.sequence.features.QuantityFeature;
030import org.biojava.nbio.core.sequence.template.AbstractSequence;
031
032import java.util.List;
033
034/**
035 * Utility methods for FASTQ formatted sequences.
036 *
037 * @since 3.0.3
038 */
039public final class FastqTools
040{
041
042        /**
043         * Private no-arg constructor.
044         */
045        private FastqTools()
046        {
047                // empty
048        }
049
050
051        /**
052         * Create and return a new {@link DNASequence} from the specified FASTQ formatted sequence.
053         *
054         * @param fastq FASTQ formatted sequence, must not be null
055         * @return a new {@link DNASequence} from the specified FASTQ formatted sequence
056         * @throws CompoundNotFoundException if DNA sequence in fastq contains unrecognised compounds
057         */
058        public static DNASequence createDNASequence(final Fastq fastq) throws CompoundNotFoundException
059        {
060                if (fastq == null)
061                {
062                        throw new IllegalArgumentException("fastq must not be null");
063                }
064                DNASequence sequence = new DNASequence(fastq.getSequence());
065                sequence.setOriginalHeader(fastq.getDescription());
066                return sequence;
067        }
068
069        /**
070         * Create and return a new {@link DNASequence} with quality scores from the specified
071         * FASTQ formatted sequence.  The quality scores are stored in a {@link QualityFeature}
072         * with a type <code>"qualityScores"</code> the same length as the sequence.
073         *
074         * @param fastq FASTQ formatted sequence, must not be null
075         * @return a new {@link DNASequence} with quality scores from the specified FASTQ formatted sequence
076         * @throws CompoundNotFoundException if DNA sequence in fastq contains unrecognised compounds
077         */
078        public static DNASequence createDNASequenceWithQualityScores(final Fastq fastq) throws CompoundNotFoundException
079        {
080                DNASequence sequence = createDNASequence(fastq);
081                sequence.addFeature(1, sequence.getLength(), createQualityScores(fastq));
082                return sequence;
083        }
084
085        /**
086         * Create and return a new {@link DNASequence} with error probabilities from the specified
087         * FASTQ formatted sequence.  The error probabilities are stored in a {@link QuantityFeature}
088         * with a type <code>"errorProbabilities"</code> the same length as the sequence.
089         *
090         * @param fastq FASTQ formatted sequence, must not be null
091         * @return a new {@link DNASequence} with error probabilities from the specified FASTQ formatted sequence
092         * @throws CompoundNotFoundException if DNA sequence in fastq contains unrecognised compounds
093         */
094        public static DNASequence createDNASequenceWithErrorProbabilities(final Fastq fastq) throws CompoundNotFoundException
095        {
096                DNASequence sequence = createDNASequence(fastq);
097                sequence.addFeature(1, sequence.getLength(), createErrorProbabilities(fastq));
098                return sequence;
099        }
100
101        /**
102         * Create and return a new {@link DNASequence} with quality scores and error probabilities from the
103         * specified FASTQ formatted sequence.  The quality scores are stored in a {@link QualityFeature}
104         * with a type <code>"qualityScores"</code> the same length as the sequence and the error
105         * probabilities are stored in a {@link QuantityFeature} with a type <code>"errorProbabilities"</code>
106         * the same length as the sequence.
107         *
108         * @param fastq FASTQ formatted sequence, must not be null
109         * @return a new {@link DNASequence} with quality scores and error probabilities from the specified
110         *    FASTQ formatted sequence
111         * @throws CompoundNotFoundException if DNA sequence in fastq contains unrecognised compounds
112         */
113        public static DNASequence createDNASequenceWithQualityScoresAndErrorProbabilities(final Fastq fastq) throws CompoundNotFoundException
114        {
115                DNASequence sequence = createDNASequence(fastq);
116                sequence.addFeature(1, sequence.getLength(), createQualityScores(fastq));
117                sequence.addFeature(1, sequence.getLength(), createErrorProbabilities(fastq));
118                return sequence;
119        }
120
121        /**
122         * Create and return a new {@link QualityFeature} from the quality scores of the specified
123         * FASTQ formatted sequence.  The quality scores feature has a type <code>"qualityScores"</code>
124         * and will be the same length as the sequence.
125         *
126         * @param fastq FASTQ formatted sequence, must not be null
127         * @return a new {@link QualityFeature} from the quality scores of the specified FASTQ
128         *    formatted sequence
129         */
130        public static QualityFeature<AbstractSequence<NucleotideCompound>, NucleotideCompound> createQualityScores(final Fastq fastq)
131        {
132                if (fastq == null)
133                {
134                        throw new IllegalArgumentException("fastq must not be null");
135                }
136                QualityFeature<AbstractSequence<NucleotideCompound>, NucleotideCompound> qualityScores = new QualityFeature<AbstractSequence<NucleotideCompound>, NucleotideCompound>("qualityScores", "sequencing");
137                qualityScores.setQualities(toList(qualityScores(fastq)));
138                return qualityScores;
139        }
140
141        /**
142         * Create and return a new {@link QuantityFeature} from the error probabilities of the specified
143         * FASTQ formatted sequence.  The error probabilities feature has a type <code>"errorProbabilities"</code>
144         * and will be the same length as the sequence.
145         *
146         * @param fastq FASTQ formatted sequence, must not be null
147         * @return a new {@link QualityFeature} from the error probabilities of the specified FASTQ
148         *    formatted sequence
149         */
150        public static QuantityFeature<AbstractSequence<NucleotideCompound>, NucleotideCompound> createErrorProbabilities(final Fastq fastq)
151        {
152                if (fastq == null)
153                {
154                        throw new IllegalArgumentException("fastq must not be null");
155                }
156                QuantityFeature<AbstractSequence<NucleotideCompound>, NucleotideCompound> errorProbabilities = new QuantityFeature<AbstractSequence<NucleotideCompound>, NucleotideCompound>("errorProbabilities", "sequencing");
157                errorProbabilities.setQuantities(toList(errorProbabilities(fastq)));
158                return errorProbabilities;
159        }
160
161        /**
162         * Return the quality scores from the specified FASTQ formatted sequence.
163         *
164         * @param fastq FASTQ formatted sequence, must not be null
165         * @return the quality scores from the specified FASTQ formatted sequence
166         */
167        public static Iterable<Number> qualityScores(final Fastq fastq)
168        {
169                if (fastq == null)
170                {
171                        throw new IllegalArgumentException("fastq must not be null");
172                }
173                int size = fastq.getQuality().length();
174                List<Number> qualityScores = Lists.newArrayListWithExpectedSize(size);
175                FastqVariant variant = fastq.getVariant();
176                for (int i = 0; i < size; i++)
177                {
178                        char c = fastq.getQuality().charAt(i);
179                        qualityScores.add(variant.qualityScore(c));
180                }
181                return ImmutableList.copyOf(qualityScores);
182        }
183
184        /**
185         * Copy the quality scores from the specified FASTQ formatted sequence into the specified int array.
186         *
187         * @param fastq FASTQ formatted sequence, must not be null
188         * @param qualityScores int array of quality scores, must not be null and must be the same
189         *    length as the FASTQ formatted sequence quality
190         * @return the specified int array of quality scores
191         */
192        public static int[] qualityScores(final Fastq fastq, final int[] qualityScores)
193        {
194                if (fastq == null)
195                {
196                        throw new IllegalArgumentException("fastq must not be null");
197                }
198                if (qualityScores == null)
199                {
200                        throw new IllegalArgumentException("qualityScores must not be null");
201                }
202                int size = fastq.getQuality().length();
203                if (qualityScores.length != size)
204                {
205                        throw new IllegalArgumentException("qualityScores must be the same length as the FASTQ formatted sequence quality");
206                }
207                FastqVariant variant = fastq.getVariant();
208                for (int i = 0; i < size; i++)
209                {
210                        char c = fastq.getQuality().charAt(i);
211                        qualityScores[i] = variant.qualityScore(c);
212                }
213                return qualityScores;
214        }
215
216        /**
217         * Return the error probabilities from the specified FASTQ formatted sequence.
218         *
219         * @param fastq FASTQ formatted sequence, must not be null
220         * @return the error probabilities from the specified FASTQ formatted sequence
221         */
222        public static Iterable<Number> errorProbabilities(final Fastq fastq)
223        {
224                if (fastq == null)
225                {
226                        throw new IllegalArgumentException("fastq must not be null");
227                }
228                int size = fastq.getQuality().length();
229                List<Number> errorProbabilities = Lists.newArrayListWithExpectedSize(size);
230                FastqVariant variant = fastq.getVariant();
231                for (int i = 0; i < size; i++)
232                {
233                        char c = fastq.getQuality().charAt(i);
234                        errorProbabilities.add(variant.errorProbability(c));
235                }
236                return ImmutableList.copyOf(errorProbabilities);
237        }
238
239        /**
240         * Copy the error probabilities from the specified FASTQ formatted sequence into the specified double array.
241         *
242         * @param fastq FASTQ formatted sequence, must not be null
243         * @param errorProbabilities double array of error probabilities, must not be null and must be the same
244         *    length as the FASTQ formatted sequence quality
245         * @return the specified double array of error probabilities
246         */
247        public static double[] errorProbabilities(final Fastq fastq, final double[] errorProbabilities)
248        {
249                if (fastq == null)
250                {
251                        throw new IllegalArgumentException("fastq must not be null");
252                }
253                if (errorProbabilities == null)
254                {
255                        throw new IllegalArgumentException("errorProbabilities must not be null");
256                }
257                int size = fastq.getQuality().length();
258                if (errorProbabilities.length != size)
259                {
260                        throw new IllegalArgumentException("errorProbabilities must be the same length as the FASTQ formatted sequence quality");
261                }
262                FastqVariant variant = fastq.getVariant();
263                for (int i = 0; i < size; i++)
264                {
265                        char c = fastq.getQuality().charAt(i);
266                        errorProbabilities[i] = variant.errorProbability(c);
267                }
268                return errorProbabilities;
269        }
270
271        /**
272         * Convert the specified FASTQ formatted sequence to the
273         * specified FASTQ sequence format variant.
274         *
275         * @since 4.2
276         * @param fastq FASTQ formatted sequence, must not be null
277         * @param variant FASTQ sequence format variant, must not be null
278         * @return the specified FASTQ formatted sequence converted to the
279         *    specified FASTQ sequence format variant
280         */
281        public static Fastq convert(final Fastq fastq, final FastqVariant variant)
282        {
283                if (fastq == null)
284                {
285                        throw new IllegalArgumentException("fastq must not be null");
286                }
287                if (variant == null)
288                {
289                        throw new IllegalArgumentException("variant must not be null");
290                }
291                if (fastq.getVariant().equals(variant))
292                {
293                        return fastq;
294                }
295                return new Fastq(fastq.getDescription(), fastq.getSequence(), convertQualities(fastq, variant), variant);
296        }
297
298        /**
299         * Convert the qualities in the specified FASTQ formatted sequence to the
300         * specified FASTQ sequence format variant.
301         *
302         * @since 4.2
303         * @param fastq FASTQ formatted sequence, must not be null
304         * @param variant FASTQ sequence format variant, must not be null
305         * @return the qualities in the specified FASTQ formatted sequence converted to the
306         *    specified FASTQ sequence format variant
307         */
308        static String convertQualities(final Fastq fastq, final FastqVariant variant)
309        {
310                if (fastq == null)
311                {
312                        throw new IllegalArgumentException("fastq must not be null");
313                }
314                if (variant == null)
315                {
316                        throw new IllegalArgumentException("variant must not be null");
317                }
318                if (fastq.getVariant().equals(variant))
319                {
320                        return fastq.getQuality();
321                }
322                int size = fastq.getQuality().length();
323                double[] errorProbabilities = errorProbabilities(fastq, new double[size]);
324                StringBuilder sb = new StringBuilder(size);
325                for (int i = 0; i < size; i++)
326                {
327                        sb.append(variant.quality(variant.qualityScore(errorProbabilities[i])));
328                }
329                return sb.toString();
330        }
331
332        /**
333         * Return the specified iterable as a list.
334         *
335         * @paam <T> element type
336         * @param iterable iterable
337         * @return the specified iterable as a list
338         */
339        @SuppressWarnings("unchecked")
340        static <T> List<T> toList(final Iterable<? extends T> iterable)
341        {
342                if (iterable instanceof List)
343                {
344                        return (List<T>) iterable;
345                }
346                return ImmutableList.copyOf(iterable);
347        }
348}