/* * The MIT License (MIT) * * Copyright (c) 2007-2015 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.broad.igv.sam; import htsjdk.samtools.*; import htsjdk.samtools.util.CloseableIterator; import org.broad.igv.feature.Range; import org.broad.igv.sam.reader.AlignmentReader; import org.broad.igv.sam.reader.AlignmentReaderFactory; import org.broad.igv.ui.panel.ReferenceFrame; import org.broad.igv.util.ResourceLocator; import org.broad.igv.util.StringUtils; import org.broad.igv.util.Utilities; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.*; /** * Write SAM/BAM Alignments to a file or stream * <p/> * * @author jacob * @since 2012/05/04 */ public class SAMWriter { private static final String SAM_FIELD_SEPARATOR = "\t"; private SAMFileHeader header; public SAMWriter(SAMFileHeader header) { this.header = header; } public int writeToFile(File outFile, Iterator<PicardAlignment> alignments, boolean createIndex) { SAMFileWriterFactory factory = new SAMFileWriterFactory(); factory.setCreateIndex(createIndex); SAMFileWriter writer = factory.makeSAMOrBAMWriter(header, true, outFile); return writeAlignments(writer, alignments); } public int writeToStream(OutputStream stream, Iterator<PicardAlignment> alignments, boolean bam) { SAMFileWriterImpl writer; // if (bam) { // return 0; // Don't know how to output bams // //writer = new BAMFileWriter(stream, null); // } else { writer = new SAMTextWriter(stream); writer.setHeader(header); return writeAlignments(writer, alignments); // } } private int writeAlignments(SAMFileWriter writer, Iterator<PicardAlignment> alignments) { int count = 0; while (alignments.hasNext()) { PicardAlignment al = alignments.next(); writer.addAlignment(al.getRecord()); count++; } writer.close(); return count; } private static int getFlags(Alignment alignment) { int result = alignment.isPaired() ? 0x1 : 0; ReadMate mate = alignment.getMate(); if (mate != null) { result += !mate.isMapped() ? 0x8 : 0; result += mate.isNegativeStrand() ? 0x20 : 0; } result += alignment.isProperPair() ? 0x2 : 0; result += !alignment.isMapped() ? 0x4 : 0; result += alignment.isNegativeStrand() ? 0x10 : 0; result += alignment.isFirstOfPair() ? 0x40 : 0; result += alignment.isSecondOfPair() ? 0x80 : 0; //TODO Not really clear on the meaning of this flag : it seems like we //can do without it though //result += false ? 0x100 : 0; result += alignment.isVendorFailedRead() ? 0x200 : 0; result += alignment.isDuplicate() ? 0x400 : 0; return result; } /** * Create SAM string from alignment. Work in progress. * Currently ignores the quality string and any optional attributes, * but should otherwise be correct. */ public static String getSAMString(Alignment alignment) { String refName = alignment.getChr(); List<String> tokens = new ArrayList<String>(11); tokens.add(alignment.getReadName()); tokens.add(Integer.toString(getFlags(alignment))); tokens.add(refName); tokens.add(Integer.toString(alignment.getAlignmentStart())); tokens.add(Integer.toString(alignment.getMappingQuality())); tokens.add(alignment.getCigarString()); ReadMate mate = alignment.getMate(); String mateRefName = mate != null ? mate.getChr() : null; if (refName.equals(mateRefName) && !SAMRecord.NO_ALIGNMENT_REFERENCE_NAME.equals(mateRefName)) { tokens.add("="); } else { tokens.add(mateRefName); } int mateStart = mate != null ? mate.getStart() : 0; tokens.add(Integer.toString(mateStart)); tokens.add(Integer.toString(alignment.getInferredInsertSize())); tokens.add(alignment.getReadSequence()); //TODO Implement quality tokens.add("*"); //tokens.add(SAMUtils.phredToFastq(alignment.getQualityArray())); //We add a newline to be consistent with samtools String out = StringUtils.join(tokens, SAM_FIELD_SEPARATOR) + "\n"; return out; //TODO Most of our alignment implementations don't have these attributes // SAMBinaryTagAndValue attribute = alignment.getBinaryAttributes(); // while (attribute != null) { // out.write(FIELD_SEPARATOR); // final String encodedTag; // if (attribute.isUnsignedArray()) { // encodedTag = tagCodec.encodeUnsignedArray(tagUtil.makeStringTag(attribute.tag), attribute.value); // } else { // encodedTag = tagCodec.encode(tagUtil.makeStringTag(attribute.tag), attribute.value); // } // out.write(encodedTag); // attribute = attribute.getNext(); // } } /** * Takes an iterator of Alignments, and returns an iterable/iterator * consisting only of the SamAlignments contained therein. * Can also be used to filter by position */ public static class SamAlignmentIterable implements Iterable<PicardAlignment>, Iterator<PicardAlignment> { private Iterator<Alignment> alignments; private PicardAlignment nextAlignment; private String chr = null; private int start = -1; private int end = -1; public SamAlignmentIterable(Iterator<Alignment> alignments, String chr, int start, int end) { this.alignments = alignments; this.chr = chr; this.start = start; this.end = end; advance(); } private void advance() { Alignment next; nextAlignment = null; while (alignments.hasNext() && nextAlignment == null) { next = alignments.next(); if (next instanceof PicardAlignment && passLocFilter(next)) { nextAlignment = (PicardAlignment) next; } } } @Override public boolean hasNext() { return nextAlignment != null; } @Override public PicardAlignment next() { if (!hasNext()) throw new NoSuchElementException("No more SamAlignments"); PicardAlignment next = nextAlignment; advance(); return next; } @Override public void remove() { //pass } @Override public Iterator<PicardAlignment> iterator() { return this; } private boolean passLocFilter(Alignment al) { return this.chr != null && this.overlaps(al.getChr(), al.getStart(), al.getEnd()); } /** * Determine whether there is any overlap between this interval and the specified interval */ private boolean overlaps(String chr, int start, int end) { return Utilities.objectEqual(this.chr, chr) && this.start <= end && this.end >= start; } } /** * Use Picard to write alignments which are already stored in memory * * @param dataManager * @param outFile * @param frame *@param sequence * @param start * @param end @return * @throws IOException */ public static int writeAlignmentFilePicard(AlignmentDataManager dataManager, File outFile, ReferenceFrame frame, String sequence, int start, int end) throws IOException { ResourceLocator inlocator = dataManager.getLocator(); checkExportableAlignmentFile(inlocator.getTypeString()); final SAMFileHeader fileHeader = dataManager.getReader().getFileHeader(); //IGV can only load files sorted in coordinate order, but they aren't always //labelled as such. fileHeader.setSortOrder(SAMFileHeader.SortOrder.coordinate); Range range = new Range(sequence, start, end); AlignmentInterval interval = dataManager.getLoadedInterval(frame); if (interval != null) { List<Alignment> alignments = new ArrayList(interval.getAlignments()); // We need to sort if soft-clipping is on, so just sort always. Its cheap. alignments.sort((o1, o2) -> o1.getAlignmentStart() - o2.getAlignmentStart()); Iterator<PicardAlignment> samIter = new SamAlignmentIterable(alignments.iterator(), sequence, start, end); SAMWriter writer = new SAMWriter(fileHeader); return writer.writeToFile(outFile, samIter, true); } else { return 0; } } /** * Use Picard to write alignment subset, as read from a file * * @param inlocator * @param outPath * @param sequence * @param start * @param end * @return */ public static int writeAlignmentFilePicard(ResourceLocator inlocator, String outPath, String sequence, int start, int end) throws IOException { checkExportableAlignmentFile(inlocator.getTypeString()); AlignmentReader reader = AlignmentReaderFactory.getReader(inlocator); CloseableIterator<PicardAlignment> iter = reader.query(sequence, start, end, false); final SAMFileHeader fileHeader = reader.getFileHeader(); SAMWriter writer = new SAMWriter(fileHeader); int count = writer.writeToFile(new File(outPath), iter, true); iter.close(); return count; } private static void checkExportableAlignmentFile(String typeString) { String[] validExts = new String[]{".cram", ".bam", ".sam", ".bam.list", ".sam.list"}; boolean isValidExt = false; for (String validExt : validExts) { isValidExt |= typeString.endsWith(validExt); } if (!isValidExt) { throw new IllegalArgumentException("Input alignment valid not valid for export"); } } }