/* * 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. */ /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.broad.igv.sam; import org.apache.log4j.Logger; import org.broad.igv.Globals; import org.broad.igv.feature.LocusScore; import org.broad.igv.feature.Strand; import org.broad.igv.track.WindowFunction; import java.awt.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @author jrobinso */ public class ReducedMemoryAlignment implements Alignment { private static Logger log = Logger.getLogger(ReducedMemoryAlignment.class); private final String cigarString; private String readName; private String chromosome; private int start; private int end; boolean negativeStrand; AlignmentBlock[] blocks; AlignmentBlock[] insertions; List<Gap> gaps; public ReducedMemoryAlignment(Alignment al, int indelLimit) { this.negativeStrand = al.isNegativeStrand(); this.readName = al.getReadName(); this.chromosome = al.getChr(); this.start = al.getStart(); this.end = al.getEnd(); this.cigarString = al.getCigarString(); AlignmentBlock[] blocks = al.getAlignmentBlocks(); if (blocks != null) { List<AlignmentBlock> rmBlocks = new ArrayList<AlignmentBlock>(blocks.length); int start = blocks[0].getStart(); int end = blocks[0].getEnd(); boolean softClip = blocks[0].isSoftClipped(); for (int i = 1; i < blocks.length; i++) { if (blocks[i].getStart() - end < indelLimit && blocks[i].isSoftClipped() == softClip) { end = blocks[i].getEnd(); } else { rmBlocks.add(new ReducedMemoryAlignmentBlock(start, end - start, softClip)); start = blocks[i].getStart(); end = blocks[i].getEnd(); softClip = blocks[i].isSoftClipped(); } } // Last one rmBlocks.add(new ReducedMemoryAlignmentBlock(start, end - start, softClip)); this.blocks = rmBlocks.toArray(new AlignmentBlock[rmBlocks.size()]); } AlignmentBlock[] insertions = al.getInsertions(); if (insertions != null) { List<AlignmentBlock> rmInsertions = new ArrayList<AlignmentBlock>(); for (AlignmentBlock b : insertions) { if (b.getLength() >= indelLimit) { rmInsertions.add(b); } } this.insertions = rmInsertions.toArray(new AlignmentBlock[rmInsertions.size()]); } List<Gap> gaps = al.getGaps(); if (gaps != null) { List<Gap> rmGaps = new ArrayList<Gap>(); for (Gap gap : gaps) { if (gap.getnBases() >= indelLimit) { rmGaps.add(gap); } } this.gaps = rmGaps; } } public String getReadName() { return null; } /** * .aligned files do not include sequence * * @return */ public String getReadSequence() { return ""; } public void setMateSequence(String sequnce) { // Ignore } public String getPairOrientation() { return ""; } public boolean isSmallInsert() { return false; } public boolean isVendorFailedRead() { return false; //To change body of implemented methods use File | Settings | File Templates. } public Color getColor() { return null; } public String getChromosome() { return chromosome; } public String getChr() { return chromosome; } @Override public String getContig() { return chromosome; } public int getAlignmentStart() { return getStart(); } public boolean contains(double location) { return location >= getStart() && location < getEnd(); } public AlignmentBlock[] getAlignmentBlocks() { return blocks; } public AlignmentBlock[] getInsertions() { return insertions; } public String getCigarString() { return "*"; } public int getInferredInsertSize() { return 0; } public int getMappingQuality() { return 255; } public ReadMate getMate() { return null; } public boolean isProperPair() { return true; } public boolean isMapped() { return true; } public boolean isPaired() { return false; } public boolean isNegativeStrand() { return negativeStrand; } public boolean isDuplicate() { return false; } public float getScore() { return 1.0f; } public LocusScore copy() { return this; } public String getClipboardString(double location, int mouseX) { return getValueString(location, mouseX, null); } public String getValueString(double position, int mouseX, WindowFunction ignored) { StringBuffer buf = new StringBuffer(); buf.append("Read name = " + readName + "<br>"); String sample = getSample(); if (sample != null) { buf.append("Sample = " + sample + "<br>"); } String readGroup = getReadGroup(); if (sample != null) { buf.append("Read group = " + readGroup + "<br>"); } buf.append("----------------------" + "<br>"); buf.append("Alignment start = " + Globals.DECIMAL_FORMAT.format(getAlignmentStart() + 1) + " (" + (isNegativeStrand() ? "-" : "+") + ")<br>"); buf.append("Cigar = " + cigarString + "<br>"); return buf.toString(); } /** * @return the start */ public int getStart() { return start; } /** * @param start the start to set */ public void setStart(int start) { this.start = start; } /** * @return the end */ public int getEnd() { return end; } public int getAlignmentEnd() { return end; } /** * @param end the end to set */ public void setEnd(int end) { this.end = end; } public byte getBase(double position) { return 0; } public byte getPhred(double position) { return 0; } public String getSample() { return null; } public String getReadGroup() { return null; } public String getLibrary() { return null; } public Object getAttribute(String key) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public List<Gap> getGaps() { return gaps; } public boolean isFirstOfPair() { return false; } public boolean isSecondOfPair() { return false; } public Strand getFirstOfPairStrand() { return isNegativeStrand() ? Strand.NEGATIVE : Strand.POSITIVE; } public Strand getSecondOfPairStrand() { return Strand.NONE; } public Strand getReadStrand() { return isNegativeStrand() ? Strand.NEGATIVE : Strand.POSITIVE; } @Override public void finish() { } @Override public boolean isPrimary() { return true; } @Override public boolean isSupplementary() { return false; } public static class ReducedMemoryAlignmentBlock implements AlignmentBlock { private int pixelStart; private int pixelEnd; ReducedMemoryAlignmentBlock(int start, int length, boolean softClipped) { this.start = start; this.length = length; this.softClipped = softClipped; } int start; int length; boolean softClipped; @Override public void setPixelRange(int s, int e) { this.pixelStart = s; this.pixelEnd = e; } @Override public boolean containsPixel(int x) { return x >= this.pixelStart && x <= this.pixelEnd; } @Override public boolean contains(int position) { int offset = position - start; return offset >= 0 && offset < getLength(); } @Override public int getLength() { return length; } @Override public byte getBase(int offset) { return 0; } @Override public byte[] getBases() { return null; } @Override public int getStart() { return start; } @Override public byte getQuality(int offset) { return 0; } @Override public byte[] getQualities() { return null; } @Override public int getEnd() { return start + length; } @Override public boolean isSoftClipped() { return softClipped; } @Override public boolean hasBases() { return false; } } // TODO -- why is this class a "Feature"? public static class ReducedMemoryAlignmentCounts implements AlignmentCounts { private int start; private int end; private int bucketSize; private int nBuckets; double[] total; private int maxCount; public ReducedMemoryAlignmentCounts(int start, int end, int bucketSize) { this.start = start; this.bucketSize = bucketSize; this.nBuckets = (end - start) / bucketSize; if (nBuckets == 0 || (end - start) % bucketSize != 0) nBuckets++; total = new double[nBuckets]; Arrays.fill(total, 0); // Adjust end to avoid fractional bucket this.end = start + nBuckets * bucketSize; } @Override public int getStart() { return start; } @Override public int getEnd() { return end; } @Override public int getBucketSize() { return bucketSize; } @Override public int getNumberOfPoints() { return nBuckets; } @Override public int getTotalCount(int pos) { int offset = (pos - start) / bucketSize; if (offset < 0 || offset >= total.length) { if (log.isDebugEnabled()) { log.debug("Position out of range: " + pos + " (valid range - " + start + "-" + end); } return 0; } else { return (int) Math.round(total[offset]); } } @Override public void incCounts(Alignment alignment) { AlignmentBlock[] blocks = alignment.getAlignmentBlocks(); if (blocks != null) { for (AlignmentBlock b : blocks) { if (!b.isSoftClipped()) { incrementBuckets(b.getStart(), b.getEnd()); } } } else { incrementBuckets(alignment.getAlignmentStart(), alignment.getAlignmentEnd()); } } private void incrementBuckets(int blockStart, int blockEnd) { int startBucket = Math.max(0, (blockStart - this.start) / bucketSize); int endBucket = Math.min(nBuckets - 1, (blockEnd - this.start) / bucketSize); for (int b = startBucket; b <= endBucket; b++) { int bucketStart = this.start + b * bucketSize; int bucketEnd = bucketStart + bucketSize; if (blockEnd >= bucketStart && blockStart <= bucketEnd) { double s = Math.max(blockStart, bucketStart); double e = Math.min(blockEnd, bucketEnd); double f = (e - s) / bucketSize; total[b] += f; if (total[b] > maxCount) maxCount = (int) Math.round(total[b]); } } } @Override public int getMaxCount(int origin, int end) { return maxCount; } @Override public String getValueStringAt(int pos) { int idx = (pos - start) / bucketSize; return idx > 0 && idx < total.length ? String.valueOf((int) Math.round(total[idx])) : ""; } // Rest is needed for the interface, but NA for reduced memory alignments @Override public String getChr() { return null; } @Override public String getContig() { return null; } @Override public int getTotalQuality(int pos) { return 0; } @Override public int getCount(int pos, byte b) { return 0; } @Override public int getNegCount(int pos, byte b) { return 0; } @Override public int getPosCount(int pos, byte b) { return 0; } @Override public int getDelCount(int pos) { return 0; } @Override public int getInsCount(int pos) { return 0; } @Override public int getQuality(int pos, byte b) { return 0; } @Override public boolean isConsensusMismatch(int pos, byte ref, String chr, float snpThreshold) { return false; } @Override public boolean isConsensusDeletion(int start, int end, float snpThreshold) { return false; } @Override public boolean isConsensusInsertion(int pos, float snpThreshold) { return false; } @Override public BisulfiteCounts getBisulfiteCounts() { return null; } @Override public boolean hasBaseCounts() { return false; } @Override public void finish() { } } }