Skip to content
Snippets Groups Projects
BamView.java 89.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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.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;
    
    tjc's avatar
    tjc committed
      private boolean isSingle = false;
      private boolean isSNPs = false;
    
    tjc's avatar
    tjc committed
      private boolean isStackView = true;
    
      private boolean isPairedStackView = false;
    
    tjc's avatar
    tjc committed
      private boolean isStrandStackView = 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);
    
    tjc's avatar
    tjc committed
      private JCheckBoxMenuItem checkBoxStackView = new JCheckBoxMenuItem("Stack View", isStackView);
    
      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);
      
    
      /** Used to colour the frames. */
    
      private Color lightGrey = new Color(200, 200, 200);
      private Color darkGreen = new Color(0, 150, 0);
    
      private Color darkOrange = new Color(255,140,0);
    
      private Color deepPink   = 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 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();
    
    tjc's avatar
    tjc committed
        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("");
      }
      
      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
       */
    
    tjc's avatar
    tjc committed
      private void readFromBamPicard(int start, int end, int bamIndex) 
              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);
    
    tjc's avatar
    tjc committed
            }
            lastLen = len;
          }
        }
        else
        {
          String refName = (String) combo.getSelectedItem();
    
          iterateOverBam(inputSam, refName, start, end, bamIndex);
    
    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)
    
        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;
        
    
    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);
    
    tjc's avatar
    tjc committed
                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++)
          {
            FeatureContigPredicate predicate = new FeatureContigPredicate(seqNames.get(i));
            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())
            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
        }
    
        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 ||
    
    tjc's avatar
    tjc committed
           lastend   != 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);
              }
    
              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)
              {
                checkBoxStackView.setSelected(true);
                isStackView = true;
                changeToStackView = true;
              }
    
              if ((!isStackView && !isStrandStackView)
                  || pixPerBase * 1.08f >= ALIGNMENT_PIX_PER_BASE)
              {
                Collections.sort(readsInView, new SAMRecordComparator());
              }
    
    tjc's avatar
    tjc committed
              else if( (isStackView || isStrandStackView) &&
                  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
        
    
        //System.out.println(start+".."+end+" " +
        //    "sequence length = "+getSequenceLength()+
        //    " pixPerBase="+pixPerBase);
        
    
    tjc's avatar
    tjc committed
        laststart = start;
        lastend   = end;
    
    tjc's avatar
    tjc committed
    	  drawBaseAlignment(g2, seqLength, pixPerBase, start, end);
    	else
    
    	    drawStackView(g2, seqLength, pixPerBase, start, end);
    
    	  else if(isPairedStackView)
    	    drawPairedStackView(g2, seqLength, pixPerBase, start, end);
    
    tjc's avatar
    tjc committed
    	  else if(isStrandStackView)
    	    drawStrandStackView(g2, seqLength, pixPerBase, start, end);
    
    	  else
    	    drawLineView(g2, seqLength, pixPerBase, start, end);
    
    	  if(isCoverage)
    	  {
    	    coveragePanel.setStartAndEnd(start, end);
    	    coveragePanel.setPixPerBase(pixPerBase);
    	    coveragePanel.repaint();
    	  }
    
    tjc's avatar
    tjc committed
    	  if(isSNPplot)
    	  {
    	    snpPanel.setStartAndEnd(start, end);
    	    snpPanel.setPixPerBase(pixPerBase);
    	    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.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(deepPink);
    
    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
        }
    
        if(lastMousePoint != null && blocks.size() > 0)
    
          refPos = blocks.get(0).getReferenceStart()+offset-refSeqStart;
    
    tjc's avatar
    tjc committed
          int xstart = refPos*ALIGNMENT_PIX_PER_BASE;
    
          
          refPos = blocks.get(blocks.size()-1).getReferenceStart()+
                   blocks.get(blocks.size()-1).getLength()+offset-refSeqStart;
    
    tjc's avatar
    tjc committed
          int xend   = (refPos+len)*ALIGNMENT_PIX_PER_BASE;
    
    
          if(lastMousePoint.getY() > ypos-11 && lastMousePoint.getY() < ypos)
          if(lastMousePoint.getX() > xstart &&
             lastMousePoint.getX() < xend)
          {
    
            mouseOverSAMRecord = samRecord;
    
            if(insertions != null)
              mouseOverInsertion = insertions.get((int)lastMousePoint.getX());
    
    tjc's avatar
    tjc committed
      }
      
    
      /**
       * Colour bases on their mapping quality.
       * @param g2
       * @param baseQuality
       */
      private void setColourByBaseQuality(Graphics2D g2, byte baseQuality)
      {
        if (baseQuality < 10)
          g2.setColor(Color.blue);
        else if (baseQuality < 20)
          g2.setColor(darkGreen);
        else if (baseQuality < 30)