/******************************************************************************* * GenPlay, Einstein Genome Analyzer * Copyright (C) 2009, 2014 Albert Einstein College of Medicine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * Authors: Julien Lajugie <julien.lajugie@einstein.yu.edu> * Nicolas Fourel <nicolas.fourel@einstein.yu.edu> * Eric Bouhassira <eric.bouhassira@einstein.yu.edu> * * Website: <http://genplay.einstein.yu.edu> ******************************************************************************/ package edu.yu.einstein.genplay.core.pileupFlattener; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import edu.yu.einstein.genplay.dataStructure.enums.ScoreOperation; import edu.yu.einstein.genplay.dataStructure.list.chromosomeWideList.SCWListView.bin.BinListViewBuilder; import edu.yu.einstein.genplay.dataStructure.list.genomeWideList.SCWList.binList.BinList; import edu.yu.einstein.genplay.dataStructure.list.listView.ListView; import edu.yu.einstein.genplay.dataStructure.scoredChromosomeWindow.ScoredChromosomeWindow; import edu.yu.einstein.genplay.exception.exceptions.ElementAddedNotSortedException; import edu.yu.einstein.genplay.exception.exceptions.ObjectAlreadyBuiltException; import edu.yu.einstein.genplay.util.FloatLists; /** * {@link PileupFlattener} for {@link BinList} * @author Julien Lajugie */ public class BinListPileupFlattener implements PileupFlattener { /** Size of the bins of the flattened list*/ private final int binSize; /** Queue containing the windows of the pileup */ private final SCWQueue windowQueue; /** Builder to create the result {@link ListView} */ private final BinListViewBuilder resultLVBuilder; /** Operation to compute the score of the result value of the flattening */ private final ScoreOperation scoreOperation; /** * Creates a new instance of {@link BinListPileupFlattener} * @param binSize size of the bins of the binlist * @param scoreOperation {@link ScoreOperation} method to compute a score from overlapping windows */ public BinListPileupFlattener(int binSize, ScoreOperation scoreOperation) { this.binSize = binSize; windowQueue = new SCWQueue(); resultLVBuilder = new BinListViewBuilder(binSize); this.scoreOperation = scoreOperation; } @Override public void addWindow(int windowStart, int windowStop, float windowScore) throws ElementAddedNotSortedException { if (windowQueue.isEmpty()) { windowQueue.add(windowStart, windowStop, windowScore); int firstBinStart = (windowStart - 1) / binSize; for (int i = 0; i < firstBinStart; i++) { resultLVBuilder.addElementToBuild(0); } } else { int lastWindowStart = windowQueue.get(windowQueue.size() - 1).getStart(); if (windowStart < lastWindowStart) { throw new ElementAddedNotSortedException(); } int firstBinStart = (((lastWindowStart - 1) / binSize) * binSize) + 1; int lastBinStop = (((windowStart - 1) / binSize) * binSize) + 1; // add the new window at the end of the queue windowQueue.add(windowStart, windowStop, windowScore); // retrieve the result of the pileup flattening flattenPileup(firstBinStart, lastBinStop); // remove the element that are already processed removeProcessedElements(lastBinStop); } } @Override public void addWindow(ScoredChromosomeWindow windowToAdd) throws ElementAddedNotSortedException, ObjectAlreadyBuiltException { addWindow(windowToAdd.getStart(), windowToAdd.getStop(), windowToAdd.getScore()); } /** * A new instance of {@link BinListPileupFlattener} containing no element. */ @Override public BinListPileupFlattener clone() { return new BinListPileupFlattener(binSize, scoreOperation); } /** * Computes the normalized score of the specified {@link ScoredChromosomeWindow}. * A normalized score is the score * @param binStart * @param binStop * @param currentRead * @return A normalized score */ private Float computeNormalizedScore(int binStart, int binStop, ScoredChromosomeWindow currentRead) { // case where the entire read fall in the bin if ((currentRead.getStart() >= binStart) && (currentRead.getStop() <= binStop)) { return currentRead.getScore(); } int readLengthBeforeBinStart = 0; if (currentRead.getStart() < binStart) { readLengthBeforeBinStart = binStart - currentRead.getStart(); } int readLengthAfterBinStop = 0; if (currentRead.getStop() > binStop) { readLengthAfterBinStop = currentRead.getStop() - binStop; } float partOfReadInsideBin = (currentRead.getSize() - (readLengthBeforeBinStart + readLengthAfterBinStop)) / (float) currentRead.getSize(); float score = currentRead.getScore() * partOfReadInsideBin; return score; } /** * Flattens the overlapping windows of the queue between the specified start and stop positions. * @param firstBinStart * @param lastBinStop */ private void flattenPileup(int firstBinStart, int lastBinStop) { // nothing to do if the start and the stop are equal if (firstBinStart != lastBinStop) { int binCount = (lastBinStop - firstBinStart) / binSize; List<List<Float>> scores = new ArrayList<List<Float>>(binCount); for (int i = 0; i < binCount; i++) { scores.add(new ArrayList<Float>()); } Iterator<ScoredChromosomeWindow> windowQueueIterator = windowQueue.listIterator(); ScoredChromosomeWindow currentRead; while (windowQueueIterator.hasNext() && ((currentRead = windowQueueIterator.next()).getStart() < lastBinStop)) { int readFirstBinStart = (((currentRead.getStart() - 1)/ binSize) * binSize) + 1; readFirstBinStart = Math.max(readFirstBinStart, firstBinStart); // we subtract one the stop because this position is excluded int readLastBinStart = (((currentRead.getStop() - 1) / binSize) * binSize) + 1; readLastBinStart = Math.min(readLastBinStart, lastBinStop - binSize); int i = 0; for (int binStart = readFirstBinStart; binStart <= readLastBinStart; binStart += binSize) { float normalizedScore = computeNormalizedScore(binStart, binStart + binSize, currentRead); scores.get(i++).add(normalizedScore); } } int i = 0; for (int binStart = firstBinStart; binStart < lastBinStop; binStart += binSize) { float score = processScoreList(scores.get(i++)); // add the completed bin to the flatten pileup resultLVBuilder.addElementToBuild(score); } } } /** * Flattens the remaining elements of the queue */ private void flush() { if (!windowQueue.isEmpty()) { int lastWindowStart = windowQueue.get(windowQueue.size() - 1).getStart(); int lastWindowStop = windowQueue.get(windowQueue.size() - 1).getStop(); int firstBinStart = (((lastWindowStart - 1) / binSize) * binSize) + 1; int lastBinStop = (((lastWindowStop) / binSize) * binSize) + binSize + 1; flattenPileup(firstBinStart, lastBinStop); } } @Override public ListView<ScoredChromosomeWindow> getListView() { flush(); return resultLVBuilder.getListView(); } /** * Computes a score for a window based on the scores of the different windows overlapping the resulting * flattened window. The score of the window is computed using the {@link ScoreOperation} set during the * construction of this {@link PileupFlattener} object. * @param currentScoreList scores of the windows overlapping the "flattened window" * @return the score computed */ private Float processScoreList(List<Float> currentScoreList) { switch (scoreOperation) { case ADDITION: return FloatLists.sum(currentScoreList); case AVERAGE: return FloatLists.average(currentScoreList); case DIVISION: throw new UnsupportedOperationException("Operation not supported: Division. Noncommutative operations are not supported."); case MAXIMUM: return FloatLists.maxNoZero(currentScoreList); case MINIMUM: return FloatLists.minNoZero(currentScoreList); case MULTIPLICATION: return FloatLists.multiply(currentScoreList); case SUBTRACTION: throw new UnsupportedOperationException("Operation not supported: Subtraction. Noncommutative operations are not supported."); default: throw new UnsupportedOperationException("Operation not supported: " + scoreOperation.name()); } } /** * Removes all the windows with a stop position smaller than the specified position. * Because windows are added in start position order, theses windows won't be involved * in future pileups. * @param position a position */ private void removeProcessedElements(int position) { int i = 0; while ((i < windowQueue.size()) && (windowQueue.get(i).getStart() <= position)) { if (windowQueue.get(i).getStop() <= position) { windowQueue.remove(i); } else { i++; } } } }