Skip to content
Snippets Groups Projects
BamView.java 92.9 KiB
Newer Older
tjc's avatar
tjc committed
/* JamView
 *
 * created: 2009
 *
 * This file is part of Artemis
 *
 * Copyright(C) 2009  Genome Research Limited
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
tjc's avatar
tjc committed
package uk.ac.sanger.artemis.components.alignment;

import java.awt.AlphaComposite;
tjc's avatar
tjc committed
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Composite;
tjc's avatar
tjc committed
import java.awt.Dimension;
tjc's avatar
tjc committed
import java.awt.FlowLayout;
tjc's avatar
tjc committed
import java.awt.FontMetrics;
tjc's avatar
tjc committed
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
tjc's avatar
tjc committed
import java.awt.Point;
import java.awt.Rectangle;
tjc's avatar
tjc committed
import java.awt.Stroke;
tjc's avatar
tjc committed
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
tjc's avatar
tjc committed
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
tjc's avatar
tjc committed
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
tjc's avatar
tjc committed
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
tjc's avatar
tjc committed
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
tjc's avatar
tjc committed
import java.io.BufferedReader;
tjc's avatar
tjc committed
import java.io.File;
tjc's avatar
tjc committed
import java.io.FileOutputStream;
import java.io.IOException;
tjc's avatar
tjc committed
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
tjc's avatar
tjc committed
import java.net.URL;
tjc's avatar
tjc committed
import java.util.Collections;
tjc's avatar
tjc committed
import java.util.Enumeration;
tjc's avatar
tjc committed
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
tjc's avatar
tjc committed
import javax.swing.JButton;
tjc's avatar
tjc committed
import javax.swing.JCheckBox;
tjc's avatar
tjc committed
import javax.swing.JCheckBoxMenuItem;
tjc's avatar
tjc committed
import javax.swing.JComboBox;
tjc's avatar
tjc committed
import javax.swing.JComponent;
tjc's avatar
tjc committed
import javax.swing.JFrame;
import javax.swing.JMenu;
tjc's avatar
tjc committed
import javax.swing.JMenuBar;
tjc's avatar
tjc committed
import javax.swing.JOptionPane;
tjc's avatar
tjc committed
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
tjc's avatar
tjc committed
import javax.swing.JScrollBar;
tjc's avatar
tjc committed
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
tjc's avatar
tjc committed
import javax.swing.JTextField;
tjc's avatar
tjc committed
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
tjc's avatar
tjc committed

import net.sf.samtools.AlignmentBlock;
tjc's avatar
tjc committed
import net.sf.samtools.SAMFileHeader;
import net.sf.samtools.SAMFileReader;
import net.sf.samtools.SAMRecord;
import net.sf.samtools.SAMSequenceRecord;
import net.sf.samtools.SAMFileReader.ValidationStringency;
import net.sf.samtools.util.CloseableIterator;

tjc's avatar
tjc committed
import uk.ac.sanger.artemis.Entry;
import uk.ac.sanger.artemis.EntryGroup;
import uk.ac.sanger.artemis.FeatureVector;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.Options;
import uk.ac.sanger.artemis.Selection;
import uk.ac.sanger.artemis.SelectionChangeEvent;
import uk.ac.sanger.artemis.SelectionChangeListener;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.SimpleEntryGroup;
import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent;
import uk.ac.sanger.artemis.components.DisplayAdjustmentListener;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.components.EntryFileDialog;
import uk.ac.sanger.artemis.components.FeatureDisplay;
import uk.ac.sanger.artemis.components.FileViewer;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.components.MessageDialog;
import uk.ac.sanger.artemis.components.SwingWorker;
import uk.ac.sanger.artemis.components.variant.FeatureContigPredicate;
import uk.ac.sanger.artemis.editor.MultiLineToolTipUI;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.io.EntryInformation;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.io.Range;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.sequence.Bases;
import uk.ac.sanger.artemis.sequence.MarkerRange;
tjc's avatar
tjc committed
import uk.ac.sanger.artemis.sequence.NoSequenceException;
import uk.ac.sanger.artemis.util.Document;
import uk.ac.sanger.artemis.util.DocumentFactory;
import uk.ac.sanger.artemis.util.OutOfRangeException;

public class BamView extends JPanel
tjc's avatar
tjc committed
                     implements DisplayAdjustmentListener, SelectionChangeListener
tjc's avatar
tjc committed
{
  private static final long serialVersionUID = 1L;
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  private List<SAMRecord> readsInView;
tjc's avatar
tjc committed
  private Hashtable<String, SAMFileReader> samFileReaderHash = new Hashtable<String, SAMFileReader>();

tjc's avatar
tjc committed
  private Hashtable<String, Integer> seqLengths = new Hashtable<String, Integer>();
  private Hashtable<String, Integer> offsetLengths;
tjc's avatar
tjc committed
  private Vector<String> seqNames = new Vector<String>();
  private List<Integer> hideBamList = new Vector<Integer>();
tjc's avatar
tjc committed

  private SAMRecordFlagPredicate samRecordFlagPredicate;
tjc's avatar
tjc committed
  private SAMRecordMapQPredicate samRecordMapQPredicate;
tjc's avatar
tjc committed
  private Bases bases;
tjc's avatar
tjc committed
  private JScrollPane jspView;
tjc's avatar
tjc committed
  private JScrollBar scrollBar;
  
tjc's avatar
tjc committed
  private JComboBox combo;
  private boolean isOrientation = false;
tjc's avatar
tjc committed
  private boolean isSingle = false;
  private boolean isSNPs = false;
  private boolean isCoverage = false;
tjc's avatar
tjc committed
  private boolean isSNPplot = false;
tjc's avatar
tjc committed
  
  private FeatureDisplay feature_display;
  private Selection selection;
  private JPanel mainPanel;
  private CoveragePanel coveragePanel;
tjc's avatar
tjc committed
  private SnpPanel snpPanel;
  private boolean showScale = true;
  private boolean logScale = false;
tjc's avatar
tjc committed
  private int nbasesInView;
  
  private int startBase = -1;
  private int endBase   = -1;
tjc's avatar
tjc committed
  private int laststart;
  private int lastend;
  private int maxUnitIncrement = 8;
  private boolean asynchronous = true;
  private boolean showBaseAlignment = false;
  private JMenu bamFilesMenu = new JMenu("BAM files");
  private JCheckBoxMenuItem logMenuItem = new JCheckBoxMenuItem("Use Log Scale", logScale);
  
  private JCheckBoxMenuItem cbStackView = new JCheckBoxMenuItem("Stack", true);
  private JCheckBoxMenuItem cbPairedStackView = new JCheckBoxMenuItem("Paired Stack");
  private JCheckBoxMenuItem cbStrandStackView = new JCheckBoxMenuItem("Strand Stack");
  private JCheckBoxMenuItem cbIsizeStackView = new JCheckBoxMenuItem("Inferred Size", false);
  private JCheckBoxMenuItem cbCoverageView = new JCheckBoxMenuItem("Coverage", false);
  private JCheckBoxMenuItem cbLastSelected;
  
  private ButtonGroup buttonGroup = new ButtonGroup();
  
  private JCheckBoxMenuItem colourByCoverageColour = new JCheckBoxMenuItem("Coverage Plot Colours");
  private JCheckBoxMenuItem baseQualityColour = new JCheckBoxMenuItem("Base Quality");
tjc's avatar
tjc committed
  private JCheckBoxMenuItem markInsertions = new JCheckBoxMenuItem("Mark Insertions", true);
    AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f);
  
  private CoveragePanel coverageView = new CoveragePanel();
  
  /** Used to colour the frames. */
  private static Color LIGHT_GREY = new Color(200, 200, 200);
  private static Color DARK_GREEN = new Color(0, 150, 0);
  private static Color DARK_ORANGE = new Color(255,140,0);
  private static Color DEEP_PINK   = new Color(139,10,80);
  private Point lastMousePoint = null;
  private SAMRecord mouseOverSAMRecord = null;
  private SAMRecord highlightSAMRecord = null;
  private String mouseOverInsertion;
  // record of where a mouse drag starts
  private int dragStart = -1;
  private static int MAX_BASES = 26000;
  private int maxHeight = 800;
tjc's avatar
tjc committed
  private boolean concatSequences = false;
tjc's avatar
tjc committed
  private int ALIGNMENT_PIX_PER_BASE;
  private JPopupMenu popup;
  private PopupMessageFrame popFrame = new PopupMessageFrame();
  private PopupMessageFrame waitingFrame = new PopupMessageFrame("waiting...");
tjc's avatar
tjc committed
  public static org.apache.log4j.Logger logger4j = 
    org.apache.log4j.Logger.getLogger(BamView.class);
  
  public BamView(List<String> bamList, 
tjc's avatar
tjc committed
                 String reference,
                 int nbasesInView)
  {
    super();
    setBackground(Color.white);
    this.bamList = bamList;
tjc's avatar
tjc committed
    this.nbasesInView = nbasesInView;
    
    // filter out unmapped reads by default
    setSamRecordFlagPredicate(
        new SAMRecordFlagPredicate(SAMRecordFlagPredicate.READ_UNMAPPED_FLAG));
    
tjc's avatar
tjc committed
    if(reference != null)
    {
tjc's avatar
tjc committed
      EntryGroup entryGroup = new SimpleEntryGroup();
tjc's avatar
tjc committed
      try
      {
        getEntry(reference,entryGroup);
      }
      catch (NoSequenceException e)
      {
        e.printStackTrace();
      }
    }
tjc's avatar
tjc committed
    
    try
    {
      readHeaderPicard();
    }
    catch(java.lang.UnsupportedClassVersionError err)
    {
      JOptionPane.showMessageDialog(null, 
          "This requires Java 1.6 or higher.", 
          "Check Java Version", JOptionPane.WARNING_MESSAGE);
    }
tjc's avatar
tjc committed
    catch (IOException e)
    {
      e.printStackTrace();
    }
tjc's avatar
tjc committed

    final javax.swing.plaf.FontUIResource font_ui_resource =
      Options.getOptions().getFontUIResource();
    
tjc's avatar
tjc committed
    Enumeration<Object> keys = UIManager.getDefaults().keys();
tjc's avatar
tjc committed
    while(keys.hasMoreElements()) 
    {
      Object key = keys.nextElement();
      Object value = UIManager.get(key);
      if(value instanceof javax.swing.plaf.FontUIResource) 
        UIManager.put(key, font_ui_resource);
    }
tjc's avatar
tjc committed

    setFont(Options.getOptions().getFont());
tjc's avatar
tjc committed
    FontMetrics fm  = getFontMetrics(getFont());
    ALIGNMENT_PIX_PER_BASE = fm.charWidth('M');
    BASE_HEIGHT = fm.getMaxAscent();
    selection = new Selection(null);
    
    MultiLineToolTipUI.initialize();
    setToolTipText("");
    
    buttonGroup.add(cbStackView);
    buttonGroup.add(cbPairedStackView);
    buttonGroup.add(cbStrandStackView);
    buttonGroup.add(cbIsizeStackView);
    buttonGroup.add(cbCoverageView);
  }
  
  public String getToolTipText()
  {
    if(mouseOverSAMRecord == null)
      return null;
    
    String msg = 
        mouseOverSAMRecord.getReadName() + "\n" + 
        mouseOverSAMRecord.getAlignmentStart() + ".." +
        mouseOverSAMRecord.getAlignmentEnd() + "\nisize=" +
tjc's avatar
tjc committed
        mouseOverSAMRecord.getInferredInsertSize() + "\nmapq=" +
        mouseOverSAMRecord.getMappingQuality()+"\nrname="+
        mouseOverSAMRecord.getReferenceName();
tjc's avatar
tjc committed
    if( mouseOverSAMRecord.getReadPairedFlag() && 
        mouseOverSAMRecord.getProperPairFlag() && 
       !mouseOverSAMRecord.getMateUnmappedFlag())
tjc's avatar
tjc committed
        "\nstrand (read/mate): "+
       (mouseOverSAMRecord.getReadNegativeStrandFlag() ? "-" : "+")+" / "+
       (mouseOverSAMRecord.getMateNegativeStrandFlag() ? "-" : "+");
    }
    else
      msg = msg +
tjc's avatar
tjc committed
        "\nstrand (read/mate): "+
       (mouseOverSAMRecord.getReadNegativeStrandFlag() ? "-" : "+");
    
    if(msg != null && mouseOverInsertion != null)
      msg = msg + "\nInsertion at:" +mouseOverInsertion;
    
    return msg;
tjc's avatar
tjc committed
  }
tjc's avatar
tjc committed
  
  /**
   * Get the BAM index file from the list
   * @param bam
   * @return
   * @throws IOException
   */
  private File getBamIndexFile(String bam) throws IOException
  {
    File bamIndexFile = null;
tjc's avatar
tjc committed
    if (bam.startsWith("http"))
    {
      final URL urlBamIndexFile = new URL(bam + ".bai");
      InputStream is = urlBamIndexFile.openStream();
tjc's avatar
tjc committed

tjc's avatar
tjc committed
      // Create temp file.
      bamIndexFile = File.createTempFile(urlBamIndexFile.getFile().replaceAll(
          "[\\/\\s]", "_"), ".bai");
      bamIndexFile.deleteOnExit();

      FileOutputStream out = new FileOutputStream(bamIndexFile);
      int c;
      while ((c = is.read()) != -1)
        out.write(c);
      out.flush();
      out.close();
      is.close();

      System.out.println("create... " + bamIndexFile.getAbsolutePath());
    }
    else
      bamIndexFile = new File(bam + ".bai");
tjc's avatar
tjc committed

    return bamIndexFile;
  }
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
  /**
   * Get the SAM file reader.
   * @param bam
   * @return
   * @throws IOException
   */
  private SAMFileReader getSAMFileReader(final String bam) throws IOException
  {  
tjc's avatar
tjc committed
    if(samFileReaderHash.containsKey(bam))
      return samFileReaderHash.get(bam);
    
    File bamIndexFile = getBamIndexFile(bam);
    final SAMFileReader samFileReader;
tjc's avatar
tjc committed
    if(!bam.startsWith("http"))
    {
      File bamFile = new File(bam);
tjc's avatar
tjc committed
      samFileReader = new SAMFileReader(bamFile, bamIndexFile);
tjc's avatar
tjc committed
    }
    else
    {
      final URL urlBamFile = new URL(bam);
      samFileReader = new SAMFileReader(urlBamFile, bamIndexFile, false);
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
    samFileReader.setValidationStringency(ValidationStringency.SILENT);
    samFileReaderHash.put(bam, samFileReader);
    
    return samFileReader;
tjc's avatar
tjc committed
  }
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  private void readHeaderPicard() throws IOException
tjc's avatar
tjc committed
  {
    String bam = bamList.get(0);
tjc's avatar
tjc committed
    final SAMFileReader inputSam = getSAMFileReader(bam);
    
    //final SAMFileReader inputSam = new SAMFileReader(bamFile, indexFile);
tjc's avatar
tjc committed
    SAMFileHeader header = inputSam.getFileHeader();
    List<SAMSequenceRecord> readGroups = header.getSequenceDictionary().getSequences();
    
    for(int i=0; i<readGroups.size(); i++)
    {
      seqLengths.put(readGroups.get(i).getSequenceName(),
                     readGroups.get(i).getSequenceLength());
      seqNames.add(readGroups.get(i).getSequenceName());
    }
tjc's avatar
tjc committed
    //inputSam.close();
tjc's avatar
tjc committed
  }
tjc's avatar
tjc committed

tjc's avatar
tjc committed
  
tjc's avatar
tjc committed
  /**
   * Read a SAM or BAM file.
tjc's avatar
tjc committed
   * @throws IOException 
tjc's avatar
tjc committed
   */
  private void readFromBamPicard(int start, int end, int bamIndex, float pixPerBase) 
tjc's avatar
tjc committed
          throws IOException
tjc's avatar
tjc committed
  {
    // Open the input file.  Automatically detects whether input is SAM or BAM
    // and delegates to a reader implementation for the appropriate format.
tjc's avatar
tjc committed
    String bam = bamList.get(bamIndex);  
    final SAMFileReader inputSam = getSAMFileReader(bam);
    
    //final SAMFileReader inputSam = new SAMFileReader(bamFile, indexFile);
tjc's avatar
tjc committed

tjc's avatar
tjc committed

    if(concatSequences)
    {
      int len = 0;
      int lastLen = 1;
      for(int i=0; i<seqNames.size(); i++)
      {
        int thisLength = seqLengths.get(seqNames.get(i));
        len += thisLength;

        if( (lastLen >= start && lastLen < end) ||
            (len >= start && len < end) ||
            (start >= lastLen && start < len) ||
            (end >= lastLen && end < len) )
        {
          int offset = getSequenceOffset(seqNames.get(i)); 
          int thisStart = start - offset;
          if(thisStart < 1)
            thisStart = 1;
          int thisEnd   = end - offset;
          if(thisEnd > thisLength)
            thisEnd = thisLength;
          
tjc's avatar
tjc committed
          //System.out.println("READ "+seqNames.get(i)+"  "+thisStart+".."+thisEnd);
          iterateOverBam(inputSam, seqNames.get(i), thisStart, thisEnd, bamIndex, pixPerBase, bam);
tjc's avatar
tjc committed
        }
        lastLen = len;
      }
    }
    else
    {
      String refName = (String) combo.getSelectedItem();
      iterateOverBam(inputSam, refName, start, end, bamIndex, pixPerBase, bam);
tjc's avatar
tjc committed
    //inputSam.close();
tjc's avatar
tjc committed
    //System.out.println("readFromBamPicard "+start+".."+end);
    //System.out.println("Reads in view ... "+readsInView.size());
tjc's avatar
tjc committed
  }
  
  /**
   * Iterate over BAM file and load into the <code>List</code> of
   * <code>SAMRecord</code>.
   * @param inputSam
   * @param refName
   * @param start
   * @param end
   */
  private void iterateOverBam(final SAMFileReader inputSam, 
                              String refName, int start, int end,
                              int bamIndex, float pixPerBase,
                              String bam)
    boolean multipleBAM = false;
    if(bamList.size() > 1)
      multipleBAM = true;
tjc's avatar
tjc committed
    CloseableIterator<SAMRecord> it = inputSam.queryOverlapping(refName, start, end);
    MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
tjc's avatar
tjc committed
    int checkMemAfter = 8000;
    int cnt = 0;
    int seqOffset = getSequenceOffset(refName);
    int offset = seqOffset- getBaseAtStartOfView();
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
    while ( it.hasNext() )
    {
      try
      {
tjc's avatar
tjc committed
        cnt++;
tjc's avatar
tjc committed
        SAMRecord samRecord = it.next();
        if( samRecordFlagPredicate == null ||
           !samRecordFlagPredicate.testPredicate(samRecord))
        {
tjc's avatar
tjc committed
          if(samRecordMapQPredicate == null ||
             samRecordMapQPredicate.testPredicate(samRecord))
            if(multipleBAM)
              samRecord.setAttribute("FL", bamIndex);
            if(isCoverageView(pixPerBase))
              coverageView.addRecord(samRecord, offset, bam);
            
            if(isCoverage)
              coveragePanel.addRecord(samRecord, offset, bam);
            if(isSNPplot)
              snpPanel.addRecord(samRecord, seqOffset);
            if(!isCoverageView(pixPerBase))
              readsInView.add(samRecord);
tjc's avatar
tjc committed
        if(cnt > checkMemAfter)
tjc's avatar
tjc committed
          cnt = 0;
          float heapFraction =
            (float)((float)memory.getHeapMemoryUsage().getUsed()/
                    (float)memory.getHeapMemoryUsage().getMax());
          logger4j.debug("Heap memory usage (used/max): "+heapFraction);
          
tjc's avatar
tjc committed
          if(readsInView.size() > checkMemAfter*2 && !waitingFrame.isVisible())
            waitingFrame.showWaiting("loading...", mainPanel);
          
          if(heapFraction > 0.90) 
tjc's avatar
tjc committed
          {
tjc's avatar
tjc committed
            popFrame.show(
              "Using > 90 % of the maximum memory limit:"+
              (memory.getHeapMemoryUsage().getMax()/1000000.f)+" Mb.\n"+
              "Not all reads in this range have been read in. Zoom in or\n"+
              "consider increasing the memory for this application.",
tjc's avatar
tjc committed
              mainPanel,
tjc's avatar
tjc committed
            break;
          }
tjc's avatar
tjc committed
      }
      catch(Exception e)
      {
        System.out.println(e.getMessage());
      }
    }
tjc's avatar
tjc committed
    it.close();
  }

  private int getSequenceLength()
  {
    if(concatSequences)
    {
      int len = 0;
      for(int i=0; i<seqNames.size(); i++)
        len += seqLengths.get(seqNames.get(i));
      return len;
    }
    else
      return seqLengths.get((String) combo.getSelectedItem());
  }
  
tjc's avatar
tjc committed
  /**
   * For BAM files with multiple references sequences, calculate
   * the offset from the start of the concatenated sequence for 
   * a given reference.
   * @param refName
   * @return
   */
tjc's avatar
tjc committed
  protected int getSequenceOffset(String refName)
  {
    if(!concatSequences)
      return 0;
/*    offsetLengths = new Hashtable<String, Integer>(combo.getItemCount());
      int offset = 0;
      for(int i=0; i<combo.getItemCount(); i++)
      {
        String thisSeqName = (String) combo.getItemAt(i);
        offsetLengths.put(thisSeqName, offset);
        offset += seqLengths.get(combo.getItemAt(i));
      }*/

      FeatureVector features = feature_display.getEntryGroup().getAllFeatures();
      offsetLengths = new Hashtable<String, Integer>(seqNames.size());
      for(int i=0; i<seqNames.size(); i++)
      {
tjc's avatar
tjc committed
        FeatureContigPredicate predicate = new FeatureContigPredicate(seqNames.get(i).trim());
        for(int j=0; j<features.size(); j++)
        {
          if(predicate.testPredicate(features.elementAt(j)))
          {
            offsetLengths.put(seqNames.get(i), features.elementAt(j).getFirstBase()-1);
            break;
          }
        }
      
      if(offsetLengths.size() != seqNames.size())
tjc's avatar
tjc committed
      {
tjc's avatar
tjc committed
        System.err.println("Found: "+offsetLengths.size() +" of "+ seqNames.size());
        JOptionPane.showMessageDialog(this, 
            "There is a problem matching the reference sequences\n"+
            "to the names in the BAM file. This may mean the labels\n"+
            "on the reference features do not match those in the in\n"+
            "the BAM file.", 
            "Problem Found", JOptionPane.WARNING_MESSAGE);
tjc's avatar
tjc committed
        concatSequences = false;
        return 0;
      }
tjc's avatar
tjc committed
    }
    return offsetLengths.get(refName);
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  /**
   * Override
   */
  protected void paintComponent(Graphics g)
tjc's avatar
tjc committed
  {
	super.paintComponent(g);
	Graphics2D g2 = (Graphics2D)g;

	mouseOverSAMRecord = null;
tjc's avatar
tjc committed
    int seqLength = getSequenceLength();
tjc's avatar
tjc committed
	float pixPerBase = getPixPerBaseByWidth();
tjc's avatar
tjc committed
	
    int start;
tjc's avatar
tjc committed
    int end;
    
    if(startBase > 0)
      start = startBase;
    else
tjc's avatar
tjc committed
      start = getBaseAtStartOfView();
    
    if(endBase > 0)
      end = endBase;
    else
tjc's avatar
tjc committed
    {
tjc's avatar
tjc committed
      end   = start + nbasesInView - 1;
      if(end > seqLength)
        end = seqLength;
tjc's avatar
tjc committed
    }
    boolean changeToStackView = false;
    MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
tjc's avatar
tjc committed
    if(laststart != start ||
       lastend   != end ||
       CoveragePanel.isRedraw())
tjc's avatar
tjc committed
    {
      if(isCoverageView(pixPerBase))
        coverageView.init(this, pixPerBase, start, end);
      if(isCoverage)
        coveragePanel.init(this, pixPerBase, start, end);
      if(isSNPplot)
        snpPanel.init(this, pixPerBase, start, end);
tjc's avatar
tjc committed
      {
        try
        {
          float heapFractionUsedBefore = (float) ((float) memory.getHeapMemoryUsage().getUsed() / 
                                                  (float) memory.getHeapMemoryUsage().getMax());

          if(readsInView == null)
            readsInView = new Vector<SAMRecord>();
          else
            readsInView.clear();
          for(int i=0; i<bamList.size(); i++)
          {
            if(!hideBamList.contains(i))
              readFromBamPicard(start, end, i, pixPerBase);
          float heapFractionUsedAfter = (float) ((float) memory.getHeapMemoryUsage().getUsed() / 
                                                 (float) memory.getHeapMemoryUsage().getMax());

          // System.out.println("Heap Max  : "+memory.getHeapMemoryUsage().getMax());
          // System.out.println("Heap Used : "+memory.getHeapMemoryUsage().getUsed());
          // System.out.println("Heap memory used "+heapFractionUsedAfter);

          if ((heapFractionUsedAfter - heapFractionUsedBefore) > 0.06
              && !isStackView() && heapFractionUsedAfter > 0.8)
            cbStackView.setSelected(true);
          if((!isStackView() && !isStrandStackView()) || isBaseAlignmentView(pixPerBase))
          {
            Collections.sort(readsInView, new SAMRecordComparator());
          }
          else if( (isStackView() || isStrandStackView()) &&
tjc's avatar
tjc committed
              bamList.size() > 1)
          {
            // merge multiple BAM files
            Collections.sort(readsInView, new SAMRecordPositionComparator(BamView.this));
tjc's avatar
tjc committed
          }
        }
        catch (OutOfMemoryError ome)
          JOptionPane.showMessageDialog(this, "Out of Memory");
          readsInView.clear();
          return;
tjc's avatar
tjc committed
        catch(IOException me)
        {
          me.printStackTrace();
        }
        catch(net.sf.samtools.util.RuntimeIOException re)
        {
          JOptionPane.showMessageDialog(this, re.getMessage());
        }
tjc's avatar
tjc committed
      }
    }
tjc's avatar
tjc committed
    laststart = start;
    lastend   = end;
    
    if(showBaseAlignment)
tjc's avatar
tjc committed
	  drawBaseAlignment(g2, seqLength, pixPerBase, start, end);
	else
	  if(isCoverageView(pixPerBase))
	    drawCoverage(g2,start, end, pixPerBase);
	  else if(isStackView())  
	    drawStackView(g2, seqLength, pixPerBase, start, end);
	  else if(isPairedStackView())
	    drawPairedStackView(g2, seqLength, pixPerBase, start, end);
	  else if(isStrandStackView())
tjc's avatar
tjc committed
	    drawStrandStackView(g2, seqLength, pixPerBase, start, end);
	  else
	    drawLineView(g2, seqLength, pixPerBase, start, end);
	  if(isCoverage)
	    coveragePanel.repaint();
tjc's avatar
tjc committed
	  if(isSNPplot)
	    snpPanel.repaint();
tjc's avatar
tjc committed
	if(waitingFrame.isVisible())
      waitingFrame.hideFrame();
tjc's avatar
tjc committed
	  popFrame.show(
          "Note :: Changed to the stack view to save memory.\n"+
          "Currently this is using "+ 
          (memory.getHeapMemoryUsage().getUsed()/1000000.f)+" Mb "+
          "and the maximum\nmemory limit is "+
          (memory.getHeapMemoryUsage().getMax()/1000000.f)+" Mb.",
tjc's avatar
tjc committed
          mainPanel,
tjc's avatar
tjc committed
  }
  
tjc's avatar
tjc committed
  
  private float getPixPerBaseByWidth()
  {
    return (float)mainPanel.getWidth() / (float)nbasesInView;
tjc's avatar
tjc committed
  }
  
  
tjc's avatar
tjc committed
  private int getMaxBasesInPanel(int seqLength)
  {
    if(feature_display == null)
tjc's avatar
tjc committed
      return seqLength+nbasesInView;
tjc's avatar
tjc committed
  
tjc's avatar
tjc committed
  /**
   * Draw the zoomed-in base view.
   * @param g2
   * @param seqLength
   * @param pixPerBase
   * @param start
   * @param end
   */
  private void drawBaseAlignment(Graphics2D g2, 
                                 int seqLength, 
                                 float pixPerBase, 
                                 final int start, 
tjc's avatar
tjc committed
  {
tjc's avatar
tjc committed
    ruler.start = start;
    ruler.end = end;
    ruler.repaint();
    
    int ypos = 0;
    String refSeq = null;
tjc's avatar
tjc committed
    int refSeqStart = start;
    
    end = start + ( mainPanel.getWidth() * ALIGNMENT_PIX_PER_BASE );
tjc's avatar
tjc committed
    if(bases != null)
    {
      // draw the reference sequence
      ypos+=11;
tjc's avatar
tjc committed
      try
      {
        int seqEnd = end+2;
tjc's avatar
tjc committed
        if(seqEnd > bases.getLength())
          seqEnd = bases.getLength();
tjc's avatar
tjc committed

        if(refSeqStart < 1)
          refSeqStart = 1;
        refSeq = 
          bases.getSubSequence(new Range(refSeqStart, seqEnd), Bases.FORWARD).toUpperCase();
tjc's avatar
tjc committed
        
        g2.setColor(LIGHT_GREY);
        g2.fillRect(0, ypos-11, mainPanel.getWidth(), 11);
        drawSelectionRange(g2, ALIGNMENT_PIX_PER_BASE, start, end);
        g2.setColor(Color.black);
tjc's avatar
tjc committed
        g2.drawString(refSeq, 0, ypos);
tjc's avatar
tjc committed
      }
      catch (OutOfRangeException e)
      {
        e.printStackTrace();
      }
    }
      drawSelectionRange(g2, ALIGNMENT_PIX_PER_BASE, start, end);
    g2.setStroke(new BasicStroke (2.f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
tjc's avatar
tjc committed
    
tjc's avatar
tjc committed
    boolean drawn[] = new boolean[readsInView.size()];
tjc's avatar
tjc committed
    for(int i=0; i<readsInView.size(); i++)
tjc's avatar
tjc committed
      drawn[i] = false;
tjc's avatar
tjc committed
    
    Rectangle r = jspView.getViewport().getViewRect();
    int nreads = readsInView.size();
    
tjc's avatar
tjc committed
    for (int i = 0; i < nreads; i++)
tjc's avatar
tjc committed
    {
tjc's avatar
tjc committed
      try
tjc's avatar
tjc committed
      {
tjc's avatar
tjc committed
        if (!drawn[i])
tjc's avatar
tjc committed
        {
tjc's avatar
tjc committed
          ypos += 11;

          SAMRecord thisRead = readsInView.get(i);
          if (ypos < r.getMaxY() || ypos > r.getMinY())
            drawSequence(g2, thisRead, ypos, refSeq, refSeqStart);
          drawn[i] = true;

          int thisEnd = thisRead.getAlignmentEnd();
          if (thisEnd == 0)
            thisEnd = thisRead.getAlignmentStart() + thisRead.getReadLength();

          for (int j = i + 1; j < nreads; j++)
tjc's avatar
tjc committed
          {
tjc's avatar
tjc committed
            if (!drawn[j])
tjc's avatar
tjc committed
            {
tjc's avatar
tjc committed
              SAMRecord nextRead = readsInView.get(j);
              int nextStart = nextRead.getAlignmentStart();
              if (nextStart > thisEnd + 1)
              {
                if (ypos < r.getMaxY() || ypos > r.getMinY())
                  drawSequence(g2, nextRead, ypos, refSeq, refSeqStart);

                drawn[j] = true;
                thisEnd = nextRead.getAlignmentEnd();
                if (thisEnd == 0)
                  thisEnd = nextStart + nextRead.getReadLength();
              }
              else if (ypos > r.getMaxY() || ypos < r.getMinY())
                break;
tjc's avatar
tjc committed
            }
          }
        }
      }
tjc's avatar
tjc committed
      catch (ArrayIndexOutOfBoundsException ae)
      {
        System.err.println(readsInView.size()+"  "+nreads);
        ae.printStackTrace();
      }
tjc's avatar
tjc committed
    }
tjc's avatar
tjc committed
    
    if(ypos > getHeight())
    {
tjc's avatar
tjc committed
      Dimension d = getPreferredSize();
      d.setSize(getPreferredSize().getWidth(), ypos);
      setPreferredSize(d);
tjc's avatar
tjc committed
      revalidate();
    }
tjc's avatar
tjc committed
  }
tjc's avatar
tjc committed
  
tjc's avatar
tjc committed
  /**
   * Draw the query sequence
   * @param g2
   * @param read
   * @param pixPerBase
   * @param ypos
   */
tjc's avatar
tjc committed
  private void drawSequence(Graphics2D g2, SAMRecord samRecord, 
                            int ypos, String refSeq, int refSeqStart)
tjc's avatar
tjc committed
  {
tjc's avatar
tjc committed
    if (!samRecord.getReadPairedFlag() ||  // read is not paired in sequencing
        samRecord.getMateUnmappedFlag() )  // mate is unmapped )  // mate is unmapped 
tjc's avatar
tjc committed
      g2.setColor(Color.black);
    else
      g2.setColor(Color.blue);
    
tjc's avatar
tjc committed
    Color col = g2.getColor();
tjc's avatar
tjc committed
    int xpos;
    int len    = 0;
    int refPos = 0;
    String readSeq = samRecord.getReadString();
tjc's avatar
tjc committed
    int offset = getSequenceOffset(samRecord.getReferenceName());
    byte[] phredQuality = null;
    if(baseQualityColour.isSelected())
      phredQuality = samRecord.getBaseQualities();

    Hashtable<Integer, String> insertions = null;
    List<AlignmentBlock> blocks = samRecord.getAlignmentBlocks();
    for(int i=0; i<blocks.size(); i++)
tjc's avatar
tjc committed
    {
      AlignmentBlock block = blocks.get(i);
      int blockStart = block.getReadStart();
      len += block.getLength();
      for(int j=0; j<block.getLength(); j++)
tjc's avatar
tjc committed
      {
        int readPos = blockStart-1+j;
        xpos = block.getReferenceStart() - 1 + j + offset;
        refPos = xpos - refSeqStart + 1;

        if(phredQuality != null)
          setColourByBaseQuality(g2, phredQuality[readPos]);
tjc's avatar
tjc committed
        if(isSNPs && refSeq != null && refPos > 0 && refPos < refSeq.length())
          if(readSeq.charAt(readPos) != refSeq.charAt(refPos))
            g2.setColor(Color.red);
          else
            g2.setColor(col);
        }
tjc's avatar
tjc committed
        g2.drawString(readSeq.substring(readPos, readPos+1), 
                      refPos*ALIGNMENT_PIX_PER_BASE, ypos);
tjc's avatar
tjc committed
      }
      // look for insertions
      if(markInsertions.isSelected() && i < blocks.size()-1)
      {
        int blockEnd = blockStart+block.getLength();
        int nextBlockStart = blocks.get(i+1).getReadStart();
        int insertSize = nextBlockStart - blockEnd;
        if(insertSize > 0)
        {
          if(insertions == null)
            insertions = new Hashtable<Integer, String>();
tjc's avatar
tjc committed

          g2.setColor(DEEP_PINK);
tjc's avatar
tjc committed

          int xscreen = (refPos+1)*ALIGNMENT_PIX_PER_BASE;
          insertions.put(xscreen, 
              readSeq.substring(blockEnd-1, nextBlockStart-1));
          g2.drawLine(xscreen, ypos, xscreen, ypos-BASE_HEIGHT);
          
          // mark on reference sequence as well
          if(bases != null)
            g2.drawLine(xscreen, 11, xscreen, 11-BASE_HEIGHT);
          g2.setColor(col);
        }
      }
      
      // highlight
      if(highlightSAMRecord != null &&
         highlightSAMRecord.getReadName().equals(samRecord.getReadName()))
      {
        refPos =  block.getReferenceStart() + offset - refSeqStart;
        int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
        int width  = block.getLength()*ALIGNMENT_PIX_PER_BASE;
        Color col1 = g2.getColor();
        g2.setColor(Color.red);
        g2.drawRect(xstart, ypos-BASE_HEIGHT, width, BASE_HEIGHT);        
        if(i < blocks.size()-1)
        {
          int nextStart = 
            (blocks.get(i+1).getReferenceStart() + offset - refSeqStart)*ALIGNMENT_PIX_PER_BASE;
          g2.drawLine(xstart+width, ypos-(BASE_HEIGHT/2), nextStart, ypos-(BASE_HEIGHT/2));
        }
        
        g2.setColor(col1);
      }
      else if(i < blocks.size()-1)
      {
        refPos =  block.getReferenceStart() + offset - refSeqStart;
        int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
        int width  = block.getLength()*ALIGNMENT_PIX_PER_BASE;
        int nextStart = 
          (blocks.get(i+1).getReferenceStart() + offset - refSeqStart)*ALIGNMENT_PIX_PER_BASE;
        g2.drawLine(xstart+width, ypos-(BASE_HEIGHT/2), nextStart, ypos-(BASE_HEIGHT/2));
      }
tjc's avatar
tjc committed
    }