/******************************************************************************* * 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.Collections; import java.util.Iterator; import java.util.List; import edu.yu.einstein.genplay.dataStructure.enums.SCWListType; import edu.yu.einstein.genplay.dataStructure.enums.ScoreOperation; import edu.yu.einstein.genplay.dataStructure.list.chromosomeWideList.SCWListView.SCWListViewBuilder; import edu.yu.einstein.genplay.dataStructure.list.chromosomeWideList.SCWListView.dense.DenseSCWListViewBuilder; import edu.yu.einstein.genplay.dataStructure.list.chromosomeWideList.SCWListView.generic.GenericSCWListViewBuilder; import edu.yu.einstein.genplay.dataStructure.list.chromosomeWideList.SCWListView.mask.MaskListViewBuilder; import edu.yu.einstein.genplay.dataStructure.list.genomeWideList.SCWList.SimpleSCWList.SimpleSCWList; import edu.yu.einstein.genplay.dataStructure.list.listView.ListView; import edu.yu.einstein.genplay.dataStructure.list.listView.ListViewBuilder; 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 SimpleSCWList} * @author Julien Lajugie */ public class SimpleSCWPileupFlattener implements PileupFlattener { /** Queue containing the windows of the pileup */ private final SCWQueue windowQueue; /** Builder to create the result {@link ListView} */ private final SCWListViewBuilder resultLVBuilder; /** Operation to compute the score of the result value of the flattening */ private final ScoreOperation scoreOperation; /** * Creates an instance of {@link SimpleSCWPileupFlattener} * @param scoreOperation {@link ScoreOperation} to compute the score of the result value of the flattening * @param scwListType type of the list that this flattener will create */ public SimpleSCWPileupFlattener(ScoreOperation scoreOperation, SCWListType scwListType) { windowQueue = new SCWQueue(); this.scoreOperation = scoreOperation; switch (scwListType) { case DENSE: resultLVBuilder = new DenseSCWListViewBuilder(); break; case GENERIC: resultLVBuilder = new GenericSCWListViewBuilder(); break; case MASK: resultLVBuilder = new MaskListViewBuilder(); break; default: // case where the input list is a binlist // the simple SCW flattener will create a generic result list resultLVBuilder = new GenericSCWListViewBuilder(); } } /** * Creates an instance of {@link SimpleSCWPileupFlattener} * @param scoreOperation scoreOperation {@link ScoreOperation} to compute the score of the result value of the flattening * @param resultLVBuilder {@link ListViewBuilder} to create the result {@link ListView} */ public SimpleSCWPileupFlattener(ScoreOperation scoreOperation, SCWListViewBuilder resultLVBuilder) { windowQueue = new SCWQueue(); this.scoreOperation = scoreOperation; this.resultLVBuilder = resultLVBuilder; } @Override public void addWindow(int windowStart, int windowStop, float windowScore) throws ElementAddedNotSortedException { if (windowQueue.isEmpty()) { windowQueue.add(windowStart, windowStop, windowScore); } else { int lastStart = windowQueue.get(windowQueue.size() - 1).getStart(); if (windowStart < lastStart) { throw new ElementAddedNotSortedException(); } // add the new window at the end of the queue windowQueue.add(windowStart, windowStop, windowScore); // retrieve the result of the pileup flattening flattenPileup(lastStart, windowStart); // remove the element that are not needed anymore removeProcessedElements(windowStart); } } @Override public void addWindow(ScoredChromosomeWindow windowToAdd) throws ElementAddedNotSortedException, ObjectAlreadyBuiltException { addWindow(windowToAdd.getStart(), windowToAdd.getStop(), windowToAdd.getScore()); } /** * A new instance of {@link SimpleSCWPileupFlattener} containing no element. */ @Override public SimpleSCWPileupFlattener clone() { return new SimpleSCWPileupFlattener(scoreOperation, resultLVBuilder.clone()); } /** * Computes a score for each window of the list of flattened windows. * The score is based on the scores of the different windows overlapping the resulting flattened window. * The score of each window is computed using the {@link ScoreOperation} set during the construction * of this {@link SimpleSCWPileupFlattener} object. * @param nodes list of the nodes (start and stop positions) from the flattening of the pileup * @return a list of score containing one score for each flattened window */ private List<Float> computeScores(List<Integer> nodes) { List<Float> scores = new ArrayList<Float>(); if ((nodes != null) && (nodes.size() > 1)) { // creates structure for scores of each flattened window List<List<Float>> scoreLists = new ArrayList<List<Float>>(); for (int i = 0; i < (nodes.size() - 1); i++) { scoreLists.add(new ArrayList<Float>()); } // retrieve list of scores for each flattened window for (int i = 0; (i + 1) < nodes.size(); i++) { int flattenedStart = nodes.get(i); int flattenedStop = nodes.get(i + 1); int j = 0; while ((j < windowQueue.size()) && (windowQueue.get(j).getStart() < flattenedStop)) { if (windowQueue.get(j).getStop() > flattenedStart) { scoreLists.get(i).add(windowQueue.get(j).getScore()); } j++; } } // compute and return score of each flattened window for (List<Float> currentScoreList: scoreLists) { scores.add(processScoreList(currentScoreList)); } } return scores; } /** * Flattens the overlapping windows of the queue between the specified start and stop positions. * The score of the windows are computed accordingly to the {@link ScoreOperation} value specified * during construction of this {@link SimpleSCWPileupFlattener} object. * @param startPosition * @param stopPosition */ private void flattenPileup(int startPosition, int stopPosition) { ScoredChromosomeWindow currentWindow; // nodes are start and stop positions of the windows resulting from the flattening process List<Integer> nodes = new ArrayList<Integer>(); Iterator<ScoredChromosomeWindow> iterator = windowQueue.iterator(); nodes.add(startPosition); while (iterator.hasNext() && ((currentWindow = iterator.next()).getStart() < stopPosition)) { if (currentWindow.getStart() > startPosition) { nodes.add(currentWindow.getStart()); } if ((currentWindow.getStop() > startPosition) && (currentWindow.getStop() < stopPosition)) { nodes.add(currentWindow.getStop()); } } nodes.add(stopPosition); // sort the nodes Collections.sort(nodes); // remove duplicate nodes removeDuplicateNodes(nodes); // compute the score values for each windows from the flattening List<Float> scores = computeScores(nodes); // generate the list of windows from the flattening process for (int i = 0; i < scores.size(); i++) { if (scores.get(i) != 0) { resultLVBuilder.addElementToBuild(nodes.get(i), nodes.get(i + 1), scores.get(i)); } } } /** * Flattens the remaining elements of the queue */ private void flush() { if (!windowQueue.isEmpty()) { int lastStart = 0; lastStart = windowQueue.get(windowQueue.size() - 1).getStart(); flattenPileup(lastStart, Integer.MAX_VALUE); } } @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 SimpleSCWPileupFlattener} 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 the duplicate values in the sorted list of nodes * @param nodes sorted list of integers */ private void removeDuplicateNodes(List<Integer> nodes) { // nothing to do in these cases if ((nodes != null) && (nodes.size() > 1)) { int previousValue = nodes.get(0); int i = 1; while (i < nodes.size()) { int currentValue = nodes.get(i); // remove if current value is equal to previous one if (currentValue == previousValue) { nodes.remove(i); } else { previousValue = currentValue; i++; } } } } /** * 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++; } } } }