/* BamView * * 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. * */ package uk.ac.sanger.artemis.components.alignment; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Composite; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.net.URL; import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Vector; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButton; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSlider; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.apache.log4j.Level; import htsjdk.samtools.AlignmentBlock; import htsjdk.samtools.SAMException; import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.SamReader; import htsjdk.samtools.SamReaderFactory; import htsjdk.samtools.ValidationStringency; import htsjdk.samtools.SamReaderFactory.Option; import htsjdk.samtools.cram.ref.ReferenceSource; import htsjdk.samtools.SAMReadGroupRecord; import htsjdk.samtools.SAMRecord; import htsjdk.samtools.SAMSequenceRecord; import htsjdk.samtools.SamInputResource; import htsjdk.samtools.util.CloseableIterator; import uk.ac.sanger.artemis.Entry; import uk.ac.sanger.artemis.EntryGroup; import uk.ac.sanger.artemis.FeatureVector; import uk.ac.sanger.artemis.Options; import uk.ac.sanger.artemis.Selection; import uk.ac.sanger.artemis.SelectionChangeEvent; import uk.ac.sanger.artemis.SelectionChangeListener; import uk.ac.sanger.artemis.circular.TextFieldInt; import uk.ac.sanger.artemis.components.DisplayAdjustmentEvent; import uk.ac.sanger.artemis.components.DisplayAdjustmentListener; import uk.ac.sanger.artemis.components.EntryEdit; import uk.ac.sanger.artemis.components.EntryFileDialog; import uk.ac.sanger.artemis.components.FeatureDisplay; import uk.ac.sanger.artemis.components.FileViewer; import uk.ac.sanger.artemis.components.IndexReferenceEvent; import uk.ac.sanger.artemis.components.MessageDialog; import uk.ac.sanger.artemis.components.NonModalDialog; import uk.ac.sanger.artemis.components.SequenceComboBox; import uk.ac.sanger.artemis.components.SwingWorker; import uk.ac.sanger.artemis.editor.MultiLineToolTipUI; import uk.ac.sanger.artemis.io.EntryInformation; import uk.ac.sanger.artemis.io.Range; import uk.ac.sanger.artemis.sequence.Bases; import uk.ac.sanger.artemis.sequence.MarkerRange; 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.FTPSeekableStream; import uk.ac.sanger.artemis.util.OutOfRangeException; /** * Handles displaying of BAM and CRAM alignment files. * If an index file is not present it will create one. * Files can be read locally, by http or ftp. * */ public class BamView extends JPanel implements DisplayAdjustmentListener, SelectionChangeListener { private static final long serialVersionUID = 1L; public static String BAM_SUFFIX = ".*\\.(bam|cram)$"; /** Whether or not we re running as a standalone BamView panel. */ private static boolean standaloneMode = false; private List readsInView; private Hashtable samFileReaderHash = new Hashtable(); private List readGroups = new Vector(); private HashMap seqLengths = new HashMap(); private HashMap offsetLengths; private Vector seqNames = new Vector(); protected List bamList; protected List hideBamList = new Vector(); private SAMRecordPredicate samRecordFlagPredicate; private SAMRecordMapQPredicate samRecordMapQPredicate; private SAMRecordFilter filterFrame; private Bases bases; private JScrollPane jspView; private JScrollBar scrollBar; private SequenceComboBox combo; private boolean isOrientation = false; private boolean isSingle = false; private boolean isSNPs = false; private boolean isCoverage = false; private boolean isSNPplot = false; private EntryEdit entry_edit; private FeatureDisplay feature_display; private Selection selection; private JPanel mainPanel = new JPanel(); private CoveragePanel coveragePanel; private SnpPanel snpPanel; private boolean logScale = false; private Ruler ruler; private int nbasesInView; private int startBase = -1; private int endBase = -1; private int laststart; private int lastend; private boolean asynchronous = true; private boolean showBaseAlignment = false; private JMenu bamFilesMenu = new JMenu("BAM/CRAM 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 cbCoverageStrandView = new JCheckBoxMenuItem("Coverage by Strand", false); private JCheckBoxMenuItem cbCoverageHeatMap = new JCheckBoxMenuItem("Coverage Heat Map", false); private JCheckBoxMenuItem cbLastSelected; private ButtonGroup buttonGroup = new ButtonGroup(); private JCheckBoxMenuItem colourByReadGrp = new JCheckBoxMenuItem("Read Group"); private JCheckBoxMenuItem colourByStrandTag = new JCheckBoxMenuItem("RNASeq Strand Specific Tag (XS)"); private JCheckBoxMenuItem colourByCoverageColour = new JCheckBoxMenuItem("Coverage Plot Colours"); private JCheckBoxMenuItem baseQualityColour = new JCheckBoxMenuItem("Base Quality"); private JCheckBoxMenuItem markInsertions = new JCheckBoxMenuItem("Mark Insertions", true); private AlphaComposite translucent = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f); private ReadGroupsFrame readGrpFrame; private GroupBamFrame groupsFrame = new GroupBamFrame(this, bamFilesMenu); 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 static Color NON_SELECTED_READ_HIGHLIGHT_COLOUR = new Color(189,103,107); private Point lastMousePoint = null; private volatile BamViewRecord mouseOverSAMRecord = null; private volatile BamViewRecord highlightSAMRecord = null; private String mouseOverInsertion; // record of where a mouse drag starts protected int dragStart = -1; private static int MAX_BASES = 26000; private int maxHeight = 800; private boolean concatSequences = false; private int ALIGNMENT_PIX_PER_BASE; private int BASE_HEIGHT; private JPopupMenu popup; private PopupMessageFrame popFrame = new PopupMessageFrame(); private PopupMessageFrame waitingFrame = new PopupMessageFrame("waiting..."); private ExecutorService bamReadTaskExecutor; private int MAX_COVERAGE = Integer.MAX_VALUE; private float readLnHgt = 2.0f; private int ftpSocketTimeout = BamUtils.getFtpSocketTimeout(); /** Whether we should strictly validate upfront the input BAM/CRAM file.*/ boolean doInputFileValidation = false; private volatile AtomicBoolean foundFatalErrors = new AtomicBoolean(false); /** busy cursor */ private Cursor cbusy = new Cursor(Cursor.WAIT_CURSOR); /** done cursor */ private Cursor cdone = new Cursor(Cursor.DEFAULT_CURSOR); /** Reference file name for CRAM */ String referenceFilename; /**Default file validation stringency level for use by SamReader - setting this to strict could cause BAM View to shut with a fatal error for some files. */ private ValidationStringency validationStringency = ValidationStringency.SILENT; public static org.apache.log4j.Logger logger4j = org.apache.log4j.Logger.getLogger(BamView.class); public BamView(List bamList, String reference, int nbasesInView, final EntryEdit entry_edit, final FeatureDisplay feature_display, final Bases bases, final JPanel containerPanel, final JFrame frame) { this(bamList, reference, nbasesInView, feature_display, bases, containerPanel, frame); this.entry_edit = entry_edit; } public BamView(List bamList, String reference, int nbasesInView, final FeatureDisplay feature_display, final Bases bases, final JPanel containerPanel, final JFrame frame) { super(); setBackground(Color.white); this.bamList = bamList; this.nbasesInView = nbasesInView; this.feature_display = feature_display; this.bases = bases; this.referenceFilename = reference; containerPanel.setLayout(new BoxLayout(containerPanel, BoxLayout.Y_AXIS)); containerPanel.add(mainPanel); // filter out unmapped reads by default setSamRecordFlagPredicate( new SAMRecordFlagPredicate(SAMRecordFlagPredicate.READ_UNMAPPED_FLAG)); if(Options.getOptions().getProperty("bam_validation_mode") != null) { String validationMode = Options.getOptions().getProperty("bam_validation_mode"); logger4j.debug("BAM FILE VALIDATION STRINGENCY=" + validationMode); if (validationMode.equalsIgnoreCase("STRICT")) { validationStringency = ValidationStringency.STRICT; } else if (validationMode.equalsIgnoreCase("LENIENT")) { validationStringency = ValidationStringency.LENIENT; } else if (validationMode.equalsIgnoreCase("SILENT")) { validationStringency = ValidationStringency.SILENT; } } doInputFileValidation = Options.getOptions().getPropertyTruthValue("bamview_perform_detailed_validation"); logger4j.debug("PERFORM UP-FRONT INPUT FILE VALIDATION=" + doInputFileValidation); if(Options.getOptions().getIntegerProperty("bam_read_thread") != null) { logger4j.debug("BAM READ THREADS="+Options.getOptions().getIntegerProperty("bam_read_thread")); bamReadTaskExecutor = Executors.newFixedThreadPool( Options.getOptions().getIntegerProperty("bam_read_thread")); } else bamReadTaskExecutor = Executors.newFixedThreadPool(1); if(Options.getOptions().getIntegerProperty("bam_max_coverage") != null) { logger4j.debug("BAM MAX COVERAGE="+Options.getOptions().getIntegerProperty("bam_max_coverage")); MAX_COVERAGE = Options.getOptions().getIntegerProperty("bam_max_coverage"); } try { readAlignmentFileHeader(); } catch(java.lang.UnsupportedClassVersionError err) { JOptionPane.showMessageDialog(null, "This requires Java 1.8 or higher.", "Check Java Version", JOptionPane.WARNING_MESSAGE); } catch (IOException ioe) { if (standaloneMode) { System.err.println(ioe.getMessage()); System.exit(0); } else { // TODO Why has this exception just been ignored? ioe.printStackTrace(); } } catch (RuntimeException e) { if (standaloneMode) { System.err.println(e.getMessage()); System.exit(0); } else { throw e; } } final javax.swing.plaf.FontUIResource font_ui_resource = Options.getOptions().getFontUIResource(); Enumeration keys = UIManager.getDefaults().keys(); 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); } setFont(Options.getOptions().getFont()); 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); buttonGroup.add(cbCoverageStrandView); buttonGroup.add(cbCoverageHeatMap); addMouseListener(new PopupListener()); jspView = new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); jspView.setViewportBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.DARK_GRAY)); Border empty = new EmptyBorder(0,0,0,0); jspView.setBorder(empty); jspView.getVerticalScrollBar().setUnitIncrement(8); addBamToPanel(frame); // apply command line options if(System.getProperty("show_snps") != null) isSNPs = true; if(System.getProperty("show_snp_plot") != null) { isSNPplot = true; snpPanel.setVisible(isSNPplot); } if(System.getProperty("show_cov_plot") != null) { isCoverage = true; coveragePanel.setVisible(isCoverage); } } public String getToolTipText() { if(isCoverageView(getPixPerBaseByWidth()) && lastMousePoint != null) return coverageView.getToolTipText( lastMousePoint.y-getJspView().getViewport().getViewPosition().y); if(mouseOverSAMRecord == null) return null; String msg = mouseOverSAMRecord.sam.getReadName() + "\n" + mouseOverSAMRecord.sam.getAlignmentStart() + ".." + mouseOverSAMRecord.sam.getAlignmentEnd() + (mouseOverSAMRecord.sam.getReadGroup() != null ? "\nRG="+mouseOverSAMRecord.sam.getReadGroup().getId() : "") + "\nisize=" + mouseOverSAMRecord.sam.getInferredInsertSize() + "\nmapq=" + mouseOverSAMRecord.sam.getMappingQuality()+ "\nrname="+ mouseOverSAMRecord.sam.getReferenceName(); if( mouseOverSAMRecord.sam.getReadPairedFlag() && mouseOverSAMRecord.sam.getProperPairFlag() && !mouseOverSAMRecord.sam.getMateUnmappedFlag()) { msg = msg + "\nstrand (read/mate): "+ (mouseOverSAMRecord.sam.getReadNegativeStrandFlag() ? "-" : "+")+" / "+ (mouseOverSAMRecord.sam.getMateNegativeStrandFlag() ? "-" : "+"); } else msg = msg + "\nstrand (read/mate): "+ (mouseOverSAMRecord.sam.getReadNegativeStrandFlag() ? "-" : "+"); if(msg != null && mouseOverInsertion != null) msg = msg + "\nInsertion at:" +mouseOverInsertion; return msg; } /** * Get the SAM file reader. * @param alignmentFile String * @return SamReader * @throws IOException for I/O issue * @throws SAMException */ private SamReader getSAMFileReader(final String alignmentFile) throws IOException, SAMException { if(samFileReaderHash.containsKey(alignmentFile)) return samFileReaderHash.get(alignmentFile); /* * Try and find the associated index file. * Create one if we can't find it. */ File indexFile = BamUtils.getIndexFile(alignmentFile); final SamReader samFileReader; CRAMReferenceSequenceFile ref = null; // Parsing of the header happens during SamReader construction, // so need to set the default stringency (from properties). // final SamReaderFactory samReaderFactory = SamReaderFactory.makeDefault(); samReaderFactory.enable(SamReaderFactory.Option.CACHE_FILE_BASED_INDEXES); samReaderFactory.disable(Option.EAGERLY_DECODE); samReaderFactory.validationStringency(validationStringency); if ( BamUtils.isCramFile(alignmentFile) ) { htsjdk.samtools.util.Log.setGlobalLogLevel( htsjdk.samtools.util.Log.LogLevel.ERROR); if (!BamView.isStandaloneMode()) { // Will only be executed from Artemis, rather than standalone mode. // ref = new CRAMReferenceSequenceFile( feature_display.getEntryGroup().getSequenceEntry(), this); samReaderFactory.referenceSource(new ReferenceSource(ref)); } else { // BamView Standalone mode // if (referenceFilename != null && referenceFilename.trim().length() > 0) { samReaderFactory.referenceSource(new uk.ac.sanger.artemis.cramtools.ref.ReferenceSource(new File(referenceFilename))); } else { // No ref file provided so the code will search for one. samReaderFactory.referenceSource(new uk.ac.sanger.artemis.cramtools.ref.ReferenceSource()); } } } /* * Create the associated Sam File Reader */ if(alignmentFile.startsWith("ftp")) { FTPSeekableStream fss = new FTPSeekableStream(new URL(alignmentFile), ftpSocketTimeout); samFileReader = samReaderFactory.open(SamInputResource.of(fss).index(indexFile) ); } else if(!alignmentFile.startsWith("http")) { File normalFile = new File(alignmentFile); samFileReader = samReaderFactory.open(SamInputResource.of(normalFile).index(indexFile) ); } else { final URL urlFile = new URL(alignmentFile); samFileReader = samReaderFactory.open(SamInputResource.of(urlFile).index(indexFile) ); } /* * Do one-off additional up-front validation of file. * May take a little while. */ if (doInputFileValidation) BamUtils.validateSAMFile(samFileReader, ref, false); samFileReaderHash.put(alignmentFile, samFileReader); readGroups.addAll(samFileReader.getFileHeader().getReadGroups()); return samFileReader; } private void readAlignmentFileHeader() throws IOException { final SamReader inputSam = getSAMFileReader(bamList.get(0)); final SAMFileHeader header = inputSam.getFileHeader(); for(SAMSequenceRecord seq: header.getSequenceDictionary().getSequences()) { seqLengths.put(seq.getSequenceName(), seq.getSequenceLength()); seqNames.add(seq.getSequenceName()); } } class BamReadTask implements Runnable { private int start; private int end; private short bamIndex; private float pixPerBase; private CountDownLatch latch; private Exception exception; BamReadTask(int start, int end, short bamIndex, float pixPerBase, CountDownLatch latch) { this.start = start; this.end = end; this.bamIndex = bamIndex; this.pixPerBase = pixPerBase; this.latch = latch; } public Exception getException() { return exception; } public short getBamIndex() { return bamIndex; } public void run() { exception = null; try { readFromAlignmentFile(start, end, bamIndex, pixPerBase) ; } catch (OutOfMemoryError ome) { throw ome; } catch(Exception e) { exception = e; } finally { latch.countDown(); } } } /** * Read a BAM or CRAM file. * @throws IOException */ private void readFromAlignmentFile(int start, int end, short bamIndex, float pixPerBase) throws IOException { // Open the input file. Automatically detects whether input is BAM or CRAM // and delegates to a reader implementation for the appropriate format. final String bam = bamList.get(bamIndex); final SamReader inputSam = getSAMFileReader(bam); if(isConcatSequences()) { for(String seq: seqNames) { int sLen = seqLengths.get(seq); int offset = getSequenceOffset(seq); int sBeg = offset+1; int sEnd = sBeg+sLen-1; if( (sBeg >= start && sBeg <= end) || (sBeg <= start && sEnd >= start) ) { int thisStart = start - offset; if(thisStart < 1) thisStart = 1; int thisEnd = end - offset; if(thisEnd > sLen) thisEnd = sLen; iterateOverBam(inputSam, seq, thisStart, thisEnd, bamIndex, pixPerBase, bam); //System.out.println("READ "+seq+" "+thisStart+".."+thisEnd+" "+start+" --- "+offset); } } } else { String refName = (String) combo.getSelectedItem(); iterateOverBam(inputSam, refName, start, end, bamIndex, pixPerBase, bam); } //inputSam.close(); } /** * Iterate over BAM file and load into the List of * SAMRecord. * @param inputSam * @param refName * @param start * @param end */ private void iterateOverBam(final SamReader inputSam, final String refName, final int start, final int end, final short bamIndex, final float pixPerBase, final String bam) { final MemoryMXBean memory = ManagementFactory.getMemoryMXBean(); final int checkMemAfter = 8000; final int seqOffset = getSequenceOffset(refName); final int offset = seqOffset- getBaseAtStartOfView(); final boolean isCoverageView = isCoverageView(pixPerBase); int cnt = 0; int nbins = 800; int binSize = ((end-start)/nbins)+1; if(binSize < 1) { binSize = 1; nbins = end-start+1; } int max = MAX_COVERAGE*binSize; if(max < 1) max = Integer.MAX_VALUE; final int cov[] = new int[nbins]; for(int i=0; i it = inputSam.queryOverlapping(refName, start, end); try { while ( it.hasNext() ) { try { cnt++; SAMRecord samRecord = it.next(); if(readGrpFrame != null && !readGrpFrame.isReadGroupVisible(samRecord.getReadGroup())) continue; if( samRecordFlagPredicate == null || !samRecordFlagPredicate.testPredicate(samRecord)) { if(samRecordMapQPredicate == null || samRecordMapQPredicate.testPredicate(samRecord)) { int abeg = samRecord.getAlignmentStart(); int aend = samRecord.getAlignmentEnd(); boolean over = false; for(int i=abeg; i nbins-1) bin = nbins-1; cov[bin]++; if(cov[bin] > max) { over = true; break; } } if(over) continue; if(isCoverageView) coverageView.addRecord(samRecord, offset, bam, colourByStrandTag.isSelected()); if(isCoverage) coveragePanel.addRecord(samRecord, offset, bam, colourByStrandTag.isSelected()); if(isSNPplot) snpPanel.addRecord(samRecord, seqOffset); if(!isCoverageView) readsInView.add(new BamViewRecord(samRecord, bamIndex)); } } if(cnt > checkMemAfter) { cnt = 0; float heapFraction = (float)((float)memory.getHeapMemoryUsage().getUsed()/ (float)memory.getHeapMemoryUsage().getMax()); logger4j.debug("Heap memory usage (used/max): "+heapFraction); if(readsInView.size() > checkMemAfter*2 && !waitingFrame.isVisible()) waitingFrame.showWaiting("loading...", mainPanel); if(heapFraction > 0.90) { 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.", mainPanel, 15000); break; } } } catch(Exception e) { System.err.println(e.getMessage()); } } } finally { it.close(); } } private int getSequenceLength() { if(isConcatSequences()) { int len = 0; for(String seq: seqNames) len += seqLengths.get(seq); return len; } else return seqLengths.get((String) combo.getSelectedItem()); } /** * For BAM files with multiple references sequences, calculate * the offset from the start of the concatenated sequence for * a given reference. * @param refName * @return */ protected int getSequenceOffset(String refName) { if(!isConcatSequences()) return 0; if(offsetLengths == null) { if(feature_display == null) { offsetLengths = new HashMap(combo.getItemCount()); int offset = 0; for(int i=0; i lookup = new HashMap(); for(int i=0; i(seqNames.size()); for(int i=0; i 0) start = startBase; else start = getBaseAtStartOfView(); if(endBase > 0) end = endBase; else { end = start + nbasesInView - 1; if(end > seqLength) end = seqLength; if(feature_display != null && nbasesInView < feature_display.getMaxVisibleBases()) nbasesInView = feature_display.getMaxVisibleBases(); } final float pixPerBase = getPixPerBaseByWidth(); boolean changeToStackView = false; MemoryMXBean memory = ManagementFactory.getMemoryMXBean(); if(laststart != start || lastend != end || CoveragePanel.isRedraw()) { 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); BamView.this.getRootPane().setCursor(cbusy); try { synchronized (this) { try { float heapFractionUsedBefore = (float) ((float) memory.getHeapMemoryUsage().getUsed() / (float) memory.getHeapMemoryUsage().getMax()); if(readsInView == null) readsInView = new Vector(); else readsInView.clear(); final CountDownLatch latch = new CountDownLatch(bamList.size()-hideBamList.size()); //long ms = System.currentTimeMillis(); List tasks = new ArrayList(); for(short i=0; i 0.06 && !isStackView() && heapFractionUsedAfter > 0.8) { cbStackView.setSelected(true); changeToStackView = true; } if((!isStackView() && !isStrandStackView()) || isBaseAlignmentView(pixPerBase)) { Collections.sort(readsInView, new SAMRecordComparator()); } else if( (isStackView() || isStrandStackView()) && bamList.size() > 1) { // merge multiple BAM/CRAM files Collections.sort(readsInView, new SAMRecordPositionComparator(BamView.this)); } } catch (OutOfMemoryError ome) { JOptionPane.showMessageDialog(this, "Out of Memory"); readsInView.clear(); return; } catch(htsjdk.samtools.util.RuntimeIOException re) { JOptionPane.showMessageDialog(this, re.getMessage()); } } } finally { try { BamView.this.getRootPane().setCursor(cdone); } catch (NullPointerException e) { // Ignore this as could happen if the panel has been closed. } } } laststart = start; lastend = end; // this needs to be synchronized when cloning BAM window synchronized(this) { if(showBaseAlignment) 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()) drawStrandStackView(g2, seqLength, pixPerBase, start, end); else drawLineView(g2, seqLength, pixPerBase, start, end); } } if(isCoverage) coveragePanel.repaint(); if(isSNPplot) snpPanel.repaint(); if(waitingFrame.isVisible()) waitingFrame.hideFrame(); if(changeToStackView) { 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.", mainPanel, 15000); } } protected void repaintBamView() { laststart = -1; repaint(); } private float getPixPerBaseByWidth() { return (float)mainPanel.getWidth() / (float)nbasesInView; } private int getMaxBasesInPanel(int seqLength) { if(feature_display == null) return seqLength+nbasesInView/3; else return seqLength+nbasesInView; } /** * 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, int end) { ruler.start = start; ruler.end = end; int ypos = 0; String refSeq = null; int refSeqStart = start; end = start + ( mainPanel.getWidth() * ALIGNMENT_PIX_PER_BASE ); if(bases != null) { try { int seqEnd = end+2; if(seqEnd > bases.getLength()) seqEnd = bases.getLength(); if(refSeqStart < 1) refSeqStart = 1; refSeq = bases.getSubSequence(new Range(refSeqStart, seqEnd), Bases.FORWARD).toUpperCase(); ruler.refSeq = refSeq; } catch (OutOfRangeException e) { e.printStackTrace(); } } ruler.repaint(); drawSelectionRange(g2, ALIGNMENT_PIX_PER_BASE, start, end, Color.PINK); g2.setStroke(new BasicStroke (2.f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); boolean drawn[] = new boolean[readsInView.size()]; for(int i=0; i r.getMinY()) drawSequence(g2, thisRead, ypos, refSeq, refSeqStart); drawn[i] = true; int thisEnd = thisRead.sam.getAlignmentEnd(); if (thisEnd == 0) thisEnd = thisRead.sam.getAlignmentStart() + thisRead.sam.getReadLength(); for (int j = i + 1; j < nreads; j++) { if (!drawn[j]) { BamViewRecord nextRead = readsInView.get(j); int nextStart = nextRead.sam.getAlignmentStart(); if (nextStart > thisEnd + 1) { if (ypos < r.getMaxY() || ypos > r.getMinY()) drawSequence(g2, nextRead, ypos, refSeq, refSeqStart); drawn[j] = true; thisEnd = nextRead.sam.getAlignmentEnd(); if (thisEnd == 0) thisEnd = nextStart + nextRead.sam.getReadLength(); } else if (ypos > r.getMaxY() || ypos < r.getMinY()) break; } } } } catch (ArrayIndexOutOfBoundsException ae) { System.err.println(readsInView.size()+" "+nreads); ae.printStackTrace(); } } if(ypos > getHeight()) { Dimension d = getPreferredSize(); d.setSize(getPreferredSize().getWidth(), ypos); setPreferredSize(d); revalidate(); } } /** * Draw the query sequence * @param g2 * @param read * @param pixPerBase * @param ypos */ private void drawSequence(final Graphics2D g2, final BamViewRecord bamViewRecord, int ypos, String refSeq, int refSeqStart) { SAMRecord samRecord = bamViewRecord.sam; if (!samRecord.getReadPairedFlag() || // read is not paired in sequencing samRecord.getMateUnmappedFlag() ) // mate is unmapped ) // mate is unmapped g2.setColor(Color.black); else g2.setColor(Color.blue); final Color col = g2.getColor(); int xpos; int len = 0; int refPos = 0; SAMRecordSequenceString readSeq = new SAMRecordSequenceString(samRecord.getReadString()); final int offset = getSequenceOffset(samRecord.getReferenceName()); byte[] phredQuality = null; if(baseQualityColour.isSelected()) phredQuality = samRecord.getBaseQualities(); Hashtable insertions = null; List blocks = samRecord.getAlignmentBlocks(); for(int i=0; i 0) setColourByBaseQuality(g2, phredQuality[readPos]); if(isSNPs && refSeq != null && refPos > 0 && refPos < refSeq.length()) { if( (!readSeq.isSecondaryAlignment()) && (Character.toUpperCase(readSeq.charAt(readPos)) != refSeq.charAt(refPos)) ) g2.setColor(Color.red); else g2.setColor(col); } g2.drawString(readSeq.substring(readPos, readPos+1), refPos*ALIGNMENT_PIX_PER_BASE, ypos); if(isSNPs) g2.setColor(col); } // 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(); g2.setColor(DEEP_PINK); int xscreen = (refPos+1)*ALIGNMENT_PIX_PER_BASE; insertions.put(xscreen, (refPos+refSeqStart+1)+" "+ 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); } } if(highlightSAMRecord != null && highlightSAMRecord.sam.getReadName().equals(bamViewRecord.sam.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); if (isThisBamRecordHighlighted(bamViewRecord) ) { // Selected read alignment g2.setColor(Color.black); } else { // For a read with the same name as selected one. g2.setColor(NON_SELECTED_READ_HIGHLIGHT_COLOUR); } 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)); } } if(lastMousePoint != null && blocks.size() > 0) { refPos = blocks.get(0).getReferenceStart()+offset-refSeqStart; int xstart = refPos*ALIGNMENT_PIX_PER_BASE; refPos = blocks.get(blocks.size()-1).getReferenceStart()+ blocks.get(blocks.size()-1).getLength()+offset-refSeqStart; int xend = (refPos+len)*ALIGNMENT_PIX_PER_BASE; if(lastMousePoint.getY() > ypos-11 && lastMousePoint.getY() < ypos) if(lastMousePoint.getX() > xstart && lastMousePoint.getX() < xend) { mouseOverSAMRecord = bamViewRecord; if(insertions != null) mouseOverInsertion = insertions.get((int)lastMousePoint.getX()); } } } /** * 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(DARK_GREEN); else if (baseQuality < 30) g2.setColor(DARK_ORANGE); else g2.setColor(Color.black); } /** * Draw inferred size view. * @param g2 * @param seqLength * @param pixPerBase * @param start * @param end */ private void drawLineView(Graphics2D g2, int seqLength, float pixPerBase, int start, int end) { drawSelectionRange(g2, pixPerBase,start, end, Color.PINK); if(isShowScale()) drawScale(g2, start, end, pixPerBase, getHeight()); final Stroke stroke = new BasicStroke (readLnHgt, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); g2.setStroke(stroke); int ydiff = (int) Math.round(1.5*readLnHgt); final int scaleHeight; if(isShowScale()) scaleHeight = 15; else scaleHeight = 0; int baseAtStartOfView = getBaseAtStartOfView(); Rectangle r = jspView.getViewport().getViewRect(); for(int i=0; i snps = getSNPs(samRecord); if( !samRecord.getReadPairedFlag() || // read is not paired in sequencing samRecord.getMateUnmappedFlag() ) // mate is unmapped { if(isSingle) { int ypos = getYPos(scaleHeight, samRecord.getReadString().length()); // (getHeight() - scaleHeight) - samRecord.getReadString().length(); if(ypos > r.getMaxY() || ypos < r.getMinY()) continue; g2.setColor(Color.black); drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff); } continue; } int ypos = getYPos(scaleHeight, Math.abs(samRecord.getInferredInsertSize())); if( (ypos > r.getMaxY() || ypos < r.getMinY()) && ypos > 0 ) continue; if(i < readsInView.size()-1) { bamViewNextRecord = readsInView.get(++i); samNextRecord = bamViewNextRecord.sam; if(samRecord.getReadName().equals(samNextRecord.getReadName())) { // draw connection between paired reads if(samRecord.getAlignmentEnd() < samNextRecord.getAlignmentStart() && (samNextRecord.getAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f) { g2.setColor(Color.LIGHT_GRAY); int offset1 = getSequenceOffset(samRecord.getReferenceName()); int end1 = samRecord.getAlignmentEnd()+offset1-baseAtStartOfView; int offset2 = getSequenceOffset(samNextRecord.getReferenceName()); int start2 = samNextRecord.getAlignmentStart()+offset2-baseAtStartOfView; drawTranslucentLine(g2, (int)(end1*pixPerBase), (int)(start2*pixPerBase), ypos); } if(colourByCoverageColour.isSelected()) g2.setColor(getColourByCoverageColour(bamViewRecord)); else if( (samRecord.getReadNegativeStrandFlag() && // strand of the query (1 for reverse) samNextRecord.getReadNegativeStrandFlag()) || (!samRecord.getReadNegativeStrandFlag() && !samNextRecord.getReadNegativeStrandFlag())) g2.setColor(Color.red); else g2.setColor(Color.blue); drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff); drawRead(g2, bamViewNextRecord, pixPerBase, ypos, baseAtStartOfView, getSNPs(samNextRecord), ydiff); } else { drawLoneRead(g2, bamViewRecord, ypos, pixPerBase, baseAtStartOfView, scaleHeight, snps, ydiff); i--; } } else { drawLoneRead(g2, bamViewRecord, ypos, pixPerBase, baseAtStartOfView, scaleHeight, snps, ydiff); } } drawYScale(g2, scaleHeight); } private int getYPos(int scaleHeight, int size) { if(!logScale) return (getHeight() - scaleHeight) - size; else { int logInfSize = (int)( Math.log(size) * 100); return (getHeight() - scaleHeight) - logInfSize; } } /** * Draw the reads as lines in vertical stacks. The reads are colour * coded as follows: * * blue - reads are unique and are paired with a mapped mate * black - reads are unique and are not paired or have an unmapped mate * green - reads are duplicates * * @param g2 * @param seqLength * @param pixPerBase * @param start * @param end */ private void drawStackView(Graphics2D g2, final int seqLength, final float pixPerBase, final int start, final int end) { drawSelectionRange(g2, pixPerBase,start, end, Color.PINK); if(isShowScale()) drawScale(g2, start, end, pixPerBase, getHeight()); final BasicStroke stroke = new BasicStroke( readLnHgt, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); g2.setStroke(stroke); final int scaleHeight; if(isShowScale()) scaleHeight = 15; else scaleHeight = 0; int ypos = (getHeight() - scaleHeight); int ydiff = (int) Math.round(1.5*readLnHgt); if(isOrientation) ydiff= 2*ydiff; int maxEnd = 0; final int baseAtStartOfView = getBaseAtStartOfView(); g2.setColor(Color.blue); final Rectangle r = jspView.getViewport().getViewRect(); for(BamViewRecord bamViewRecord: readsInView) { SAMRecord samRecord = bamViewRecord.sam; int offset = getSequenceOffset(samRecord.getReferenceName()); int recordStart = samRecord.getAlignmentStart()+offset; int recordEnd = samRecord.getAlignmentEnd()+offset; List snps = getSNPs(samRecord); if(colourByStrandTag.isSelected()) { if(samRecord.getAttribute("XS") == null) g2.setColor(Color.BLACK); else if( ((Character)samRecord.getAttribute("XS")).equals('+') ) g2.setColor(Color.BLUE); else if( ((Character)samRecord.getAttribute("XS")).equals('-') ) g2.setColor(Color.RED); else g2.setColor(Color.BLACK); } else if(colourByCoverageColour.isSelected()) g2.setColor(getColourByCoverageColour(bamViewRecord)); else if(colourByReadGrp.isSelected()) g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, samRecord.getReadGroup())); else if (samRecord.getDuplicateReadFlag()) g2.setColor(DARK_GREEN); // Duplicate else if (!samRecord.getReadPairedFlag() || // read is not paired in sequencing samRecord.getMateUnmappedFlag() ) // mate is unmapped ) // mate is unmapped g2.setColor(Color.black); else g2.setColor(Color.blue); if(maxEnd < recordStart || ypos < 0) { ypos = (getHeight() - scaleHeight)-ydiff; maxEnd = recordEnd+2; } else ypos = ypos-ydiff; if(ypos > r.getMaxY() || ypos < r.getMinY()) continue; drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff); } } /** * Draw the reads as lines in vertical stacks. The reads are colour * coded as follows: * * blue - reads are unique and are paired with a mapped mate * black - reads are unique and are not paired or have an unmapped mate * green - reads are duplicates * * @param g2 * @param seqLength * @param pixPerBase * @param start * @param end */ private void drawStrandStackView(Graphics2D g2, int seqLength, float pixPerBase, int start, int end) { drawSelectionRange(g2, pixPerBase,start, end, Color.PINK); final BasicStroke stroke = new BasicStroke( readLnHgt, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); final int scaleHeight = 15; drawScale(g2, start, end, pixPerBase, ((getHeight()+scaleHeight)/2)); int ymid = (getHeight()/ 2); int ydiff = (int) Math.round(1.5*readLnHgt); if(isOrientation) ydiff= 2*ydiff; g2.setStroke(stroke); // positive strand drawStrand(g2, false, scaleHeight, ymid-(scaleHeight/2), -ydiff, pixPerBase); // negative strand drawStrand(g2, true, scaleHeight, ymid+(scaleHeight/2), ydiff, pixPerBase); } private void drawStrand(Graphics2D g2, boolean isStrandNegative, int scaleHeight, int ymid, int ystep, float pixPerBase) { int hgt = getHeight(); int ypos = (hgt - scaleHeight); int maxEnd = 0; int baseAtStartOfView = getBaseAtStartOfView(); g2.setColor(Color.blue); Rectangle r = jspView.getViewport().getViewRect(); for(BamViewRecord bamViewRecord: readsInView) { SAMRecord samRecord = bamViewRecord.sam; if( isNegativeStrand(samRecord, colourByStrandTag.isSelected()) == isStrandNegative ) { final int offset = getSequenceOffset(samRecord.getReferenceName()); final int recordStart = samRecord.getAlignmentStart()+offset; final int recordEnd = samRecord.getAlignmentEnd()+offset; List snps = getSNPs(samRecord); if(colourByStrandTag.isSelected()) { if(samRecord.getAttribute("XS") == null) g2.setColor(Color.BLACK); else if( ((Character)samRecord.getAttribute("XS")).equals('+') ) g2.setColor(Color.BLUE); else if( ((Character)samRecord.getAttribute("XS")).equals('-') ) g2.setColor(Color.RED); else g2.setColor(Color.BLACK); } else if(colourByCoverageColour.isSelected()) g2.setColor(getColourByCoverageColour(bamViewRecord)); else if(colourByReadGrp.isSelected()) g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, samRecord.getReadGroup())); else if (samRecord.getDuplicateReadFlag()) g2.setColor(DARK_GREEN); // Duplicate else if (!samRecord.getReadPairedFlag() || // read is not paired in sequencing samRecord.getMateUnmappedFlag() ) // mate is unmapped g2.setColor(Color.black); else g2.setColor(Color.blue); if(maxEnd < recordStart || ypos < 0 || ypos > hgt) { ypos = ymid + ystep; maxEnd = recordEnd+2; } else ypos = ypos + ystep; if(ypos > r.getMaxY() || ypos < r.getMinY()) continue; drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ystep); } } } /** * Draw paired reads as lines in a vertical stacks. * @param g2 * @param seqLength * @param pixPerBase * @param start * @param end */ private void drawPairedStackView(Graphics2D g2, final int seqLength, final float pixPerBase, final int start, final int end) { drawSelectionRange(g2, pixPerBase,start, end, Color.PINK); if(isShowScale()) drawScale(g2, start, end, pixPerBase, getHeight()); final Vector pairedReads = new Vector(); for(int i=0; i lastEnd) { ypos = getHeight() - scaleHeight - ydiff; if(pr.sam2 != null) lastEnd = pr.sam2.sam.getAlignmentEnd(); else lastEnd = pr.sam1.sam.getAlignmentEnd(); } else ypos = ypos - ydiff; if(ypos > r.getMaxY() || ypos < r.getMinY()) continue; g2.setStroke(originalStroke); if( highlightSAMRecord != null && highlightSAMRecord.sam.getReadName().equals(pr.sam1.sam.getReadName()) ) g2.setColor(Color.black); else g2.setColor(Color.gray); if(pr.sam2 != null) { if(!readsOverlap(pr.sam1.sam, pr.sam2.sam)) { int offset1 = getSequenceOffset(pr.sam1.sam.getReferenceName()); int offset2 = getSequenceOffset(pr.sam2.sam.getReferenceName()); drawTranslucentJointedLine(g2, (int)((pr.sam1.sam.getAlignmentEnd()+offset1-getBaseAtStartOfView())*pixPerBase), (int)((pr.sam2.sam.getAlignmentStart()+offset2-getBaseAtStartOfView())*pixPerBase), ypos); } } else if(!pr.sam1.sam.getMateUnmappedFlag() && pr.sam1.sam.getProperPairFlag() && pr.sam1.sam.getMateReferenceName().equals(pr.sam1.sam.getReferenceName())) { final int prStart, prEnd; if(pr.sam1.sam.getAlignmentStart() > pr.sam1.sam.getMateAlignmentStart()) { prStart = pr.sam1.sam.getMateAlignmentStart(); prEnd = pr.sam1.sam.getAlignmentStart(); } else { prStart = pr.sam1.sam.getAlignmentEnd(); prEnd = pr.sam1.sam.getMateAlignmentStart(); } int offset = getSequenceOffset(pr.sam1.sam.getReferenceName()); drawTranslucentJointedLine(g2, (int)( (prStart+offset-getBaseAtStartOfView())*pixPerBase), (int)( (prEnd +offset-getBaseAtStartOfView())*pixPerBase), ypos); } if(colourByCoverageColour.isSelected()) g2.setColor(getColourByCoverageColour(pr.sam1)); else if(colourByStrandTag.isSelected()) { if( ((Character)pr.sam1.sam.getAttribute("XS")).equals('+') ) g2.setColor(Color.BLUE); else if( ((Character)pr.sam1.sam.getAttribute("XS")).equals('-') ) g2.setColor(Color.RED); else g2.setColor(Color.BLACK); } else if(colourByReadGrp.isSelected()) g2.setColor(getReadGroupFrame().getReadGroupColour(readGroups, pr.sam1.sam.getReadGroup())); else if( pr.sam2 != null && !( pr.sam1.sam.getReadNegativeStrandFlag() ^ pr.sam2.sam.getReadNegativeStrandFlag() ) ) g2.setColor(Color.red); else g2.setColor(Color.blue); Color c = g2.getColor(); drawRead(g2, pr.sam1, pixPerBase, ypos, baseAtStartOfView, getSNPs(pr.sam1.sam), ydiff); if(pr.sam2 != null) { g2.setColor(c); drawRead(g2, pr.sam2, pixPerBase, ypos, baseAtStartOfView, getSNPs(pr.sam2.sam), ydiff); } } } /** * Check if a record is on the negative strand. If the RNA strand specific * checkbox is set then use the RNA strand. * @param samRecord * @param useStrandTag - strand specific tag * @return */ protected static boolean isNegativeStrand(final SAMRecord samRecord, final boolean useStrandTag) { if(useStrandTag) { if(samRecord.getAttribute("XS") == null) return samRecord.getReadNegativeStrandFlag(); if( ((Character)samRecord.getAttribute("XS")).equals('+') ) return false; else return true; } return samRecord.getReadNegativeStrandFlag(); } /** * Check if two records are from the same BAM file * @param sam1 * @param sam2 * @param bamList * @return */ private boolean isFromSameBamFile(final BamViewRecord sam1, final BamViewRecord sam2, final List bamList) { if(bamList == null || bamList.size()<2) return true; final short o1 = sam1.bamIndex; final short o2 = sam2.bamIndex; if(o1 != -1 && o2 != -1) if( o1 != o2 ) return false; return true; } /** * Check if two records overlap * @param s1 * @param s2 * @return true id the two reads overlap */ private boolean readsOverlap(final SAMRecord s1, final SAMRecord s2) { if( (s2.getAlignmentStart() >= s1.getAlignmentStart() && s2.getAlignmentStart() <= s1.getAlignmentEnd()) || (s2.getAlignmentEnd() >= s1.getAlignmentStart() && s2.getAlignmentEnd() <= s1.getAlignmentEnd()) ) return true; if( (s1.getAlignmentStart() >= s2.getAlignmentStart() && s1.getAlignmentStart() <= s2.getAlignmentEnd()) || (s1.getAlignmentEnd() >= s2.getAlignmentStart() && s1.getAlignmentEnd() <= s2.getAlignmentEnd()) ) return true; return false; } /** * Draw the read coverage. * @param g2 * @param start * @param end * @param pixPerBase */ private void drawCoverage(Graphics2D g2, int start, int end, float pixPerBase) { int scaleHeight = 0; if(isShowScale()) { drawScale(g2, start, end, pixPerBase, getHeight()); scaleHeight = 15; } int hgt = jspView.getVisibleRect().height-scaleHeight; if(!cbCoverageStrandView.isSelected() && !coverageView.isPlotHeatMap()) { try { int y = getHeight()-6-( (hgt* MAX_COVERAGE)/(coverageView.max/coverageView.windowSize) ); g2.setColor(Color.YELLOW); // draw the threshold for the coverage max read cut-off g2.fillRect(0, y, getWidth(), 8); } catch(Exception e){} } g2.translate(0, getJspView().getViewport().getViewPosition().y); coverageView.drawSelectionRange(g2, pixPerBase, start, end, getHeight(), Color.PINK); coverageView.draw(g2, getWidth(), hgt, hideBamList); if(!coverageView.isPlotHeatMap()) coverageView.drawMax(g2, coverageView.getMaxCoverage()); } /** * Draw a read that apparently has a read mate that is not in view. * @param g2 * @param thisRead * @param ypos * @param pixPerBase * @param originalStroke * @param stroke */ private void drawLoneRead(Graphics2D g2, BamViewRecord bamViewRecord, int ypos, float pixPerBase, int baseAtStartOfView, int scaleHeight, List snps, int ydiff) { SAMRecord samRecord = bamViewRecord.sam; boolean offTheTop = false; int offset = getSequenceOffset(samRecord.getReferenceName()); int thisStart = samRecord.getAlignmentStart()+offset; int thisEnd = thisStart + samRecord.getReadString().length() -1; if(ypos <= 0) { offTheTop = true; ypos = samRecord.getReadString().length(); } if(samRecord.getInferredInsertSize() == 0) { offTheTop = true; ypos = getHeight() - scaleHeight - 5; } if(samRecord.getInferredInsertSize() != 0 && Math.abs(samRecord.getMateAlignmentStart()-samRecord.getAlignmentEnd())*pixPerBase > 2.f) { g2.setColor(Color.LIGHT_GRAY); if(samRecord.getAlignmentEnd() < samRecord.getMateAlignmentStart()) { int nextStart = (int)((samRecord.getMateAlignmentStart()-getBaseAtStartOfView()+offset)*pixPerBase); drawTranslucentLine(g2, (int)((thisEnd-getBaseAtStartOfView())*pixPerBase), nextStart, ypos); } else { int nextStart = (int)((samRecord.getMateAlignmentStart()-getBaseAtStartOfView()+offset)*pixPerBase); drawTranslucentLine(g2, (int)((thisStart-getBaseAtStartOfView())*pixPerBase), nextStart, ypos); } } if(colourByCoverageColour.isSelected()) g2.setColor(getColourByCoverageColour(bamViewRecord)); else if(offTheTop) g2.setColor(DARK_ORANGE); else if(samRecord.getReadNegativeStrandFlag() && samRecord.getMateNegativeStrandFlag()) // strand of the query (1 for reverse) g2.setColor(Color.red); else g2.setColor(Color.blue); drawRead(g2, bamViewRecord, pixPerBase, ypos, baseAtStartOfView, snps, ydiff); /*if (isSNPs) showSNPsOnReads(g2, samRecord, pixPerBase, ypos, offset);*/ } private void drawScale(Graphics2D g2, int start, int end, float pixPerBase, int ypos) { g2.setColor(Color.black); g2.drawLine( 0, ypos-14, (int)((end - getBaseAtStartOfView())*pixPerBase), ypos-14); int interval = end-start; if(interval > 256000) drawTicks(g2, start, end, pixPerBase, 512000, ypos); else if(interval > 64000) drawTicks(g2, start, end, pixPerBase, 12800, ypos); else if(interval > 16000) drawTicks(g2, start, end, pixPerBase, 3200, ypos); else if(interval > 4000) drawTicks(g2, start, end, pixPerBase, 800, ypos); else if(interval > 1000) drawTicks(g2, start, end, pixPerBase, 400, ypos); else drawTicks(g2, start, end, pixPerBase, 100, ypos); } private void drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division, int ypos) { int markStart = (Math.round(start/division)*division); if(markStart < 1) markStart = 1; int sm = markStart-(division/2); float x; if(sm > start) { x = (sm-getBaseAtStartOfView())*pixPerBase; g2.drawLine((int)x, ypos-14,(int)x, ypos-12); } for(int m=markStart; m 0 && count < 15 && start > 1) { g2.drawLine(0, ypos, 2, ypos); g2.drawString(Integer.toString(start), 3, ypos); start = start*5; ypos = getYPos(xScaleHeight, start); count++; } return; } for(int i=100; i snps, final int ydiff) { SAMRecord thisRead = bamViewRecord.sam; int offset = getSequenceOffset(thisRead.getReferenceName()); int thisStart = thisRead.getAlignmentStart()+offset-baseAtStartOfView; int thisEnd = thisRead.getAlignmentEnd()+offset-baseAtStartOfView; if(highlightSAMRecord != null && highlightSAMRecord.sam.getReadName().equals(thisRead.getReadName())) { Stroke originalStroke = g2.getStroke(); Stroke stroke = new BasicStroke (readLnHgt*1.6f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); g2.setStroke(stroke); Color c = g2.getColor(); if (isThisBamRecordHighlighted(bamViewRecord) ) { // Selected read alignment g2.setColor(Color.black); } else { // For a read with the same name as selected one. g2.setColor(NON_SELECTED_READ_HIGHLIGHT_COLOUR); } g2.drawLine((int)( thisStart * pixPerBase), ypos, (int)( thisEnd * pixPerBase), ypos); g2.setColor(c); g2.setStroke(originalStroke); } if(thisRead.getCigar().getCigarElements().size() == 1) g2.drawLine((int)( thisStart * pixPerBase), ypos, (int)( thisEnd * pixPerBase), ypos); else { List blocks = thisRead.getAlignmentBlocks(); Color c = g2.getColor(); int lastEnd = 0; for(int i=0; i 0 && blockStart != lastEnd) { g2.setColor(Color.gray); g2.drawLine((int)( blockStart * pixPerBase), ypos, (int)( lastEnd * pixPerBase), ypos); g2.setColor(c); } lastEnd = blockEnd; } } if(isOrientation) drawArrow(g2, thisRead, thisStart, thisEnd, pixPerBase, ypos, ydiff); // test if the mouse is over this read if(lastMousePoint != null) { if(lastMousePoint.getY() > ypos-(Math.abs(ydiff)/2) && lastMousePoint.getY() < ypos+(Math.abs(ydiff)/2)) { if(lastMousePoint.getX() > thisStart * pixPerBase && lastMousePoint.getX() < thisEnd * pixPerBase) { mouseOverSAMRecord = bamViewRecord; } } } if (isSNPs && snps != null && snps.size() > 0) showSNPsOnReads(snps, g2, pixPerBase, ypos); } /** * Draw arrow on the read to indicate orientation. * @param g2 * @param thisRead * @param thisStart * @param thisEnd * @param pixPerBase * @param ypos */ private void drawArrow(final Graphics2D g2, final SAMRecord thisRead, final int thisStart, final int thisEnd, final float pixPerBase, int ypos, int ydiff) { if(ydiff < 0) ydiff = -ydiff; if(thisRead.getReadNegativeStrandFlag()) { ypos-=readLnHgt/2; int apos = ypos + ydiff - 1; g2.drawLine((int)( (thisStart+5) * pixPerBase), apos, (int)( thisStart * pixPerBase), ypos); } else { ypos+=readLnHgt/2; int apos = ypos - ydiff + 1; g2.drawLine((int)( (thisEnd-5) * pixPerBase), apos, (int)( thisEnd * pixPerBase), ypos); } } /** * Highlight a selected range * @param g2 * @param pixPerBase * @param start * @param end */ private void drawSelectionRange(Graphics2D g2, float pixPerBase, int start, int end, Color c) { if(getSelection() != null) { Range selectedRange = getSelection().getSelectionRange(); if(selectedRange != null) { int rangeStart = selectedRange.getStart(); int rangeEnd = selectedRange.getEnd(); if(end < rangeStart || start > rangeEnd) return; int x = (int) (pixPerBase*(rangeStart-getBaseAtStartOfView())); int width = (int) (pixPerBase*(rangeEnd-rangeStart+1)); g2.setColor(c); g2.fillRect(x, 0, width, getHeight()); } } } /** * Draw a translucent line * @param g2 * @param start * @param end * @param ypos */ private void drawTranslucentLine(Graphics2D g2, int start, int end, int ypos) { Composite origComposite = g2.getComposite(); g2.setComposite(translucent); g2.drawLine(start, ypos, end, ypos); g2.setComposite(origComposite); } /** * Draw a translucent line * @param g2 * @param start * @param end * @param ypos */ private void drawTranslucentJointedLine(Graphics2D g2, int start, int end, int ypos) { Composite origComposite = g2.getComposite(); g2.setComposite(translucent); int mid = (int) ((end-start)/2.f)+start; //g2.drawLine(start, ypos, end, ypos); g2.drawLine(start, ypos, mid, ypos-5); g2.drawLine(mid, ypos-5, end, ypos); g2.setComposite(origComposite); } /** * Display the SNPs for the given read. * @param snps * @param g2 * @param pixPerBase * @param ypos */ private void showSNPsOnReads(final List snps, final Graphics2D g2, float pixPerBase, final int ypos) { final Stroke originalStroke = g2.getStroke(); final BasicStroke stroke = new BasicStroke( 1.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); g2.setStroke(stroke); g2.setColor(Color.red); for(int pos: snps) g2.drawLine((int) (pos * pixPerBase), ypos + 2, (int) (pos * pixPerBase), ypos - 2); g2.setStroke(originalStroke); } /** * Get the SNP positions * @param samRecord */ private List getSNPs(final SAMRecord samRecord) { if(!isSNPs) // return null if not displaying SNPs return null; int rbeg = samRecord.getAlignmentStart(); int rend = samRecord.getAlignmentEnd(); int offset = getSequenceOffset(samRecord.getReferenceName()); ArrayList snps = null; // use alignment blocks of the contiguous alignment of // subsets of read bases to a reference sequence try { final char[] refSeq = bases.getSubSequenceC( new Range(rbeg+offset, rend+offset), Bases.FORWARD); final byte[] readSeq = samRecord.getReadBases(); if (readSeq == null || readSeq.length == 0) { // Can occur for secondary alignments return null; } offset = offset - getBaseAtStartOfView(); final List blocks = samRecord.getAlignmentBlocks(); for(AlignmentBlock block: blocks) { int readStart = block.getReadStart(); int refStart = block.getReferenceStart(); int len = block.getLength(); for(int j=0; j(); snps.add(refPos+offset); } } } } catch (OutOfRangeException e) { System.err.println(samRecord.getReadName()+" "+e.getMessage()); } return snps; } /** * Add the alignment view to the supplied JPanel in * a JScrollPane. * @param mainPanel panel to add the alignment to * @param frame * @param autohide automatically hide the top panel containing the buttons * @param feature_display */ private void addBamToPanel(final JFrame frame) { final JComponent topPanel = bamTopPanel(frame); mainPanel.setPreferredSize(new Dimension(900, 400)); setDisplay(1, nbasesInView, null); mainPanel.setLayout(new BorderLayout()); if(topPanel instanceof JPanel) mainPanel.add(topPanel, BorderLayout.NORTH); mainPanel.add(jspView, BorderLayout.CENTER); JPanel bottomPanel = new JPanel(new BorderLayout()); coveragePanel = new CoveragePanel(this); bottomPanel.add(coveragePanel, BorderLayout.CENTER); // snpPanel = new SnpPanel(this, bases); bottomPanel.add(snpPanel, BorderLayout.NORTH); if(feature_display == null) { scrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 1, nbasesInView, 1, getMaxBasesInPanel(getSequenceLength())); scrollBar.setUnitIncrement(nbasesInView/20); scrollBar.addAdjustmentListener(new AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent e) { repaint(); if(isSNPplot) snpPanel.repaint(); if(isCoverage) coveragePanel.repaint(); } }); bottomPanel.add(scrollBar, BorderLayout.SOUTH); } else { if(!isConcatSequences()) { int seqLen = seqLengths.get((String) combo.getSelectedItem()); int artemisSeqLen = feature_display.getSequenceLength(); if(seqLen != artemisSeqLen) { int newIndex = -1; for(int i=0; i bamFiles = bamFileSelection.getFiles(BAM_SUFFIX); short count = (short) bamList.size(); bamList.addAll(bamFileSelection.getFiles(BAM_SUFFIX)); for(short i=0; i 1) yBox.add(allRefSeqs); final ReadCountDialog opts = new ReadCountDialog(new JFrame(), "RPKM Options", feature_display, yBox); if(opts.getStatus() == -1) return; int seqlen = 0; if(feature_display != null) seqlen = feature_display.getSequenceLength(); else if(bases != null) seqlen = bases.getLength(); new MappedReads(BamView.this, features, seqlen, !overlap.isSelected(), spliced.isSelected(), allRefSeqs.isSelected(), colourByStrandTag.isSelected()); } }); final JMenuItem createFeatures = new JMenuItem("Create features from coverage peaks ..."); analyse.add(createFeatures); if(feature_display == null) createFeatures.setEnabled(false); createFeatures.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(feature_display == null) return; new CreateFeatures(groupsFrame); } }); for(short i=0; i(bamList)); } }); menu.add(new JSeparator()); menu.add(bamSplitter); // JMenu coverageMenu = new JMenu("Coverage Options"); coverageView.init(this, 0.f, 0, 0); coverageView.createMenus(coverageMenu); viewMenu.add(new JSeparator()); viewMenu.add(coverageMenu); } private ReadGroupsFrame getReadGroupFrame() { if(readGrpFrame == null) readGrpFrame = new ReadGroupsFrame(readGroups, BamView.this); return readGrpFrame; } private JComponent bamTopPanel(final JFrame frame) { final JComponent topPanel; if(frame == null) { topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0)); if(feature_display != null) this.selection = feature_display.getSelection(); } else { topPanel = new JMenuBar(); frame.setJMenuBar((JMenuBar)topPanel); JMenu fileMenu = new JMenu("File"); topPanel.add(fileMenu); JMenuItem readBam = new JMenuItem("Open new BamView ..."); fileMenu.add(readBam); readBam.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String[] s = { "NEW-BAMVIEW" }; BamView.main(s); } }); JMenuItem saveAs = new JMenuItem("Save As Image File (png/jpeg/svg) ..."); fileMenu.add(saveAs); saveAs.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { PrintBamView.print((JPanel)mainPanel.getParent()); } }); JMenuItem close = new JMenuItem("Close"); fileMenu.add(close); close.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { close(); } }); JMenuItem exit = new JMenuItem("Exit"); fileMenu.add(new JSeparator()); fileMenu.add(exit); exit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int status = JOptionPane.showConfirmDialog(BamView.this, "Exit BamView?", "Exit", JOptionPane.OK_CANCEL_OPTION); if(status != JOptionPane.OK_OPTION) return; System.exit(0); } }); addKeyListener(new KeyAdapter() { public void keyPressed(final KeyEvent event) { switch (event.getKeyCode()) { case KeyEvent.VK_UP: setZoomLevel((int) (BamView.this.nbasesInView * 1.1)); break; case KeyEvent.VK_DOWN: if (showBaseAlignment) break; setZoomLevel((int) (BamView.this.nbasesInView * .9)); break; default: break; } } }); } if(seqNames.size() > 1) { int len = 0; for(int i=0; i= MAX_BASES) { cbLastSelected = getSelectedCheckBoxMenuItem(); cbCoverageView.setSelected(true); coverageView.setPlotByStrand(false); } else if(isCoverageView(pixPerBase) && nbasesInView < MAX_BASES && cbLastSelected != null) { cbLastSelected.setSelected(true); cbLastSelected = null; } jspView.setColumnHeaderView(null); if(isCoverageView(pixPerBase)) setViewportBtm(); else if (isStrandStackView()) setViewportMidPoint(); showBaseAlignment = false; baseQualityColour.setEnabled(false); markInsertions.setEnabled(false); } if(scrollBar != null) { scrollBar.setValues(startValue, nbasesInView, 1, getMaxBasesInPanel(getSequenceLength())); scrollBar.setUnitIncrement(nbasesInView/20); scrollBar.setBlockIncrement(nbasesInView); } } /** * Set the start and end base positions to display. * @param start * @param end * @param event */ public void setDisplay(int start, int end, DisplayAdjustmentEvent event) { this.startBase = start; this.endBase = end; this.nbasesInView = end-start+1; lastMousePoint = null; float pixPerBase; if(jspView.getViewport().getViewRect().width > 0) pixPerBase = getPixPerBaseByWidth(); else { if(feature_display == null) pixPerBase = 1000.f/(float)(end-start+1); else pixPerBase = feature_display.getWidth()/(float)(end-start+1); } Dimension d = new Dimension(); d.setSize(nbasesInView*pixPerBase, maxHeight); setPreferredSize(d); if(event == null) { this.startBase = -1; this.endBase = -1; } } /** * Return an Artemis entry from a file * @param entryFileName * @param entryGroup * @return * @throws NoSequenceException */ private Entry getEntry(final String entryFileName, final EntryGroup entryGroup) throws NoSequenceException { final Document entry_document = DocumentFactory.makeDocument(entryFileName); final EntryInformation artemis_entry_information = Options.getArtemisEntryInformation(); //System.out.println(entryFileName); final uk.ac.sanger.artemis.io.Entry new_embl_entry = EntryFileDialog.getEntryFromFile(null, entry_document, artemis_entry_information, false); if(new_embl_entry == null) // the read failed return null; Entry entry = null; try { if(entryGroup.getSequenceEntry() != null) bases = entryGroup.getSequenceEntry().getBases(); if(bases == null) { entry = new Entry(new_embl_entry); bases = entry.getBases(); } else entry = new Entry(bases,new_embl_entry); entryGroup.add(entry); } catch(OutOfRangeException e) { new MessageDialog(null, "read failed: one of the features in " + entryFileName + " has an out of range " + "location: " + e.getMessage()); } return entry; } private boolean isShowScale() { return (feature_display == null ? true : false); } public JScrollPane getJspView() { return jspView; } /** * Handle a mouse drag event on the drawing canvas. **/ private void handleCanvasMouseDrag(final MouseEvent event) { if(event.getButton() == MouseEvent.BUTTON3 || bases == null) return; // KJP: Removed this as it causes selection issues and // doesn't seem strictly necessary. // highlightSAMRecord = null; if(event.getClickCount() > 1) { getSelection().clear(); repaint(); return; } highlightRange(event, MouseEvent.BUTTON1_DOWN_MASK & MouseEvent.BUTTON2_DOWN_MASK); } /** * * @param event * @param onmask */ protected void highlightRange(MouseEvent event, int onmask) { int seqLength = getSequenceLength(); float pixPerBase = getPixPerBaseByWidth(); int start = (int) ( ( (event.getPoint().getX())/pixPerBase) + getBaseAtStartOfView() ); if(start < 1) start = 1; if(start > seqLength) start = seqLength; if (dragStart < 0 && (event.getModifiersEx() & onmask) == onmask) dragStart = start; else if((event.getModifiersEx() & onmask) != onmask) dragStart = -1; MarkerRange drag_range; try { if(dragStart < 0) drag_range = new MarkerRange (bases.getForwardStrand(), start, start); else drag_range = new MarkerRange (bases.getForwardStrand(), dragStart, start); getSelection().setMarkerRange(drag_range); repaint(); } catch (OutOfRangeException e) { e.printStackTrace(); } } /** * Get the colour for the given read given to it by the coverage plot. * @param samRecord * @return */ private Color getColourByCoverageColour(BamViewRecord samRecord) { short fileIndex = 0; if(bamList.size()>1) fileIndex = samRecord.bamIndex; return getColourByCoverageColour(fileIndex); } private Color getColourByCoverageColour(final short fileIndex) { LineAttributes lines[] = CoveragePanel.getLineAttributes(bamList.size()); return lines[fileIndex].getLineColour(); } protected int getMaxBases() { return MAX_BASES; } protected void setMaxBases(int max) { MAX_BASES = max; } private boolean isStackView() { return cbStackView.isSelected(); } private boolean isPairedStackView() { return cbPairedStackView.isSelected(); } private boolean isStrandStackView() { return cbStrandStackView.isSelected(); } private boolean isCoverageView(float pixPerBase) { if(isBaseAlignmentView(pixPerBase)) return false; return cbCoverageView.isSelected() || cbCoverageStrandView.isSelected() || cbCoverageHeatMap.isSelected(); } private boolean isIsizeStackView() { return cbIsizeStackView.isSelected(); } private boolean isBaseAlignmentView(float pixPerBase) { if(pixPerBase*1.08f >= ALIGNMENT_PIX_PER_BASE) return true; return false; } private JCheckBoxMenuItem getSelectedCheckBoxMenuItem() { if(isStackView()) return cbStackView; if(isPairedStackView()) return cbPairedStackView; if(isStrandStackView()) return cbStrandStackView; if(isIsizeStackView()) return cbIsizeStackView; if(cbCoverageView.isSelected()) return cbCoverageView; if(cbCoverageHeatMap.isSelected()) return cbCoverageHeatMap; return cbCoverageStrandView; } protected Selection getSelection() { return selection; } protected List getReadsInView() { return readsInView; } protected int getBasesInView() { return nbasesInView; } protected void setHighlightSAMRecord(BamViewRecord highlightSAMRecord) { this.highlightSAMRecord = highlightSAMRecord; } protected BamViewRecord getHighlightSAMRecord() { return highlightSAMRecord; } protected FeatureDisplay getFeatureDisplay() { return feature_display; } /** * @return the combo */ public SequenceComboBox getCombo() { return combo; } protected Hashtable getSamFileReaderHash() { return samFileReaderHash; } protected Vector getSeqNames() { return seqNames; } protected HashMap getSeqLengths() { return seqLengths; } protected HashMap getOffsetLengths() { return offsetLengths; } private String getVersion() { final ClassLoader cl = this.getClass().getClassLoader(); try { String line; InputStream in = cl.getResourceAsStream("etc/versions"); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); while((line = reader.readLine()) != null) { if(line.startsWith("BamView")) return line.substring( "BamView".length() ).trim(); } reader.close(); in.close(); } catch (Exception ex) { } return null; } /** * Open another BamView window */ public void openBamView(final List bamsList) { BamView bamView = new BamView(bamsList, null, nbasesInView, entry_edit, feature_display, bases, (JPanel) mainPanel.getParent(), null); bamView.getJspView().getVerticalScrollBar().setValue( bamView.getJspView().getVerticalScrollBar().getMaximum()); getJspView().getVerticalScrollBar().setValue( bamView.getJspView().getVerticalScrollBar().getMaximum()); int start = getBaseAtStartOfView(); setDisplay(start, nbasesInView+start, null); if(feature_display != null) { feature_display.addDisplayAdjustmentListener(bamView); feature_display.getSelection().addSelectionChangeListener(bamView); if(entry_edit != null) entry_edit.getOneLinePerEntryDisplay().addDisplayAdjustmentListener(bamView); if(feature_display.getEntryGroup().getSequenceEntry().getEMBLEntry().getSequence() instanceof uk.ac.sanger.artemis.io.IndexFastaStream) { if(entry_edit != null) { // add reference sequence selection listeners entry_edit.getEntryGroupDisplay().getIndexFastaCombo().addIndexReferenceListener(bamView.getCombo()); bamView.getCombo().addIndexReferenceListener(entry_edit.getEntryGroupDisplay().getIndexFastaCombo()); } } } } /** * Artemis event notification */ public void displayAdjustmentValueChanged(final DisplayAdjustmentEvent event) { if(event.getType() == DisplayAdjustmentEvent.REV_COMP_EVENT && event.isRevCompDisplay()) JOptionPane.showMessageDialog(this, "Flipping the display is not supported by BamView.", "Warning", JOptionPane.WARNING_MESSAGE); if(!asynchronous) { // if not asynchronous displayAdjustmentWork(event); return; } SwingWorker worker = new SwingWorker() { public Object construct() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if(event.getStart() != ((FeatureDisplay)event.getSource()).getForwardBaseAtLeftEdge()) { waitingFrame.showWaiting("waiting...", mainPanel); return null; } displayAdjustmentWork(event); waitingFrame.setVisible(false); return null; } }; worker.start(); } /** * Carry out the display agjustment event action. * @param event */ private void displayAdjustmentWork(final DisplayAdjustmentEvent event) { if(event.getType() == DisplayAdjustmentEvent.SCALE_ADJUST_EVENT) { laststart = -1; BamView.this.startBase = event.getStart(); BamView.this.endBase = event.getEnd(); int width = feature_display.getMaxVisibleBases(); setZoomLevel(width); repaint(); } else { setDisplay(event.getStart(), event.getStart()+feature_display.getMaxVisibleBases(), event); repaint(); } } public void selectionChanged(SelectionChangeEvent event) { repaint(); } private class Ruler extends JPanel { private static final long serialVersionUID = 1L; protected int start; protected int end; protected String refSeq; public Ruler() { super(); setPreferredSize(new Dimension(mainPanel.getWidth(), 26)); setBackground(Color.white); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; drawBaseScale(g2, start, end, 12); } private void drawBaseScale(Graphics2D g2, int start, int end, int ypos) { int startMark = (((int)(start/10))*10)+1; if(end > getSequenceLength()) end = getSequenceLength(); for(int i=startMark; i 1) getSelection().clear(); else if(e.getButton() == MouseEvent.BUTTON1) { if(isCoverageView(getPixPerBaseByWidth())) coverageView.singleClick(e.isShiftDown(), e.getPoint().y-getJspView().getViewport().getViewPosition().y); } else highlightRange(e, MouseEvent.BUTTON2_DOWN_MASK); } public void mousePressed(final MouseEvent e) { highlightSAMRecord = mouseOverSAMRecord; repaint(); javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { maybeShowPopup(e); } }); } public void mouseReleased(MouseEvent e) { dragStart = -1; maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if(e.isPopupTrigger()) { // // main menu options if(popup == null) { popup = new JPopupMenu(); createMenus(popup); } // // coverage heatmap menu options if(coverageMenu != null) popup.remove(coverageMenu); if(isCoverageView(getPixPerBaseByWidth()) && coverageView.isPlotHeatMap()) { if(coverageMenu == null) { coverageMenu = new JMenu("Coverage HeatMap"); coverageView.createMenus(coverageMenu); final JCheckBoxMenuItem coverageGrid = new JCheckBoxMenuItem("Show heatmap grid", false); coverageGrid.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { coverageView.showLabels(coverageGrid.isSelected()); } }); coverageMenu.add(coverageGrid); createGroup = new JMenuItem("Create group from selected BAMs/CRAMs"); createGroup.addActionListener(new ActionListener() { private int n = 1; public void actionPerformed(ActionEvent e) { String groupName = "group_"+n; groupsFrame.addGroup(groupName); final List selected = coverageView.getSelected(); for(String sel: selected) groupsFrame.addToGroup((new File(sel)).getName(), groupName); groupsFrame.updateAndDisplay(); n++; } }); coverageMenu.add(createGroup); } createGroup.setEnabled(coverageView.hasSelectedBams()); popup.add(coverageMenu); } if(gotoMateMenuItem != null) popup.remove(gotoMateMenuItem); if(showDetails != null) popup.remove(showDetails); if( mouseOverSAMRecord != null && mouseOverSAMRecord.sam.getReadPairedFlag() && !mouseOverSAMRecord.sam.getMateUnmappedFlag() ) { highlightSAMRecord = mouseOverSAMRecord; final BamViewRecord thisSAMRecord = mouseOverSAMRecord; gotoMateMenuItem = new JMenuItem("Go to mate of : "+ thisSAMRecord.sam.getReadName()); gotoMateMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String name = thisSAMRecord.sam.getMateReferenceName(); if(name.equals("=")) name = thisSAMRecord.sam.getReferenceName(); int offset = getSequenceOffset(name); if(feature_display != null) feature_display.makeBaseVisible( thisSAMRecord.sam.getMateAlignmentStart()+offset); else scrollBar.setValue( thisSAMRecord.sam.getMateAlignmentStart()+offset- (nbasesInView/2)); highlightSAMRecord = thisSAMRecord; } }); popup.add(gotoMateMenuItem); } if( mouseOverSAMRecord != null) { highlightSAMRecord = mouseOverSAMRecord; final BamViewRecord thisSAMRecord = mouseOverSAMRecord; showDetails = new JMenuItem("Show details of : "+ thisSAMRecord.sam.getReadName()); showDetails.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { BamView.this.getRootPane().setCursor(cbusy); BamView.this.getRootPane().repaint(); BamView.this.getRootPane().revalidate(); SwingUtilities.invokeLater( new Runnable() { public void run() { try { openFileViewer(thisSAMRecord.sam, getMate(thisSAMRecord), bamList); } finally { BamView.this.getRootPane().setCursor(cdone); } } } ); } }); popup.add(showDetails); } popup.show(e.getComponent(), e.getX(), e.getY()); } } } protected static void openFileViewer(SAMRecord readRecord, SAMRecord mateRecord, List bamList) { FileViewer viewDetail = new FileViewer(readRecord.getReadName(), true, false, false); appendToDetailView(readRecord, mateRecord, viewDetail, bamList); } private static void appendToDetailView(final SAMRecord sam, final SAMRecord mate, final FileViewer viewer, final List bamList) { if(bamList.size() > 1 && sam.getAttribute("FL") != null) { int bamIdx = (Integer)sam.getAttribute("FL"); if(bamIdx < bamList.size()) viewer.appendString("File "+bamList.get(bamIdx)+"\n\n", Level.INFO); } viewer.appendString("Read Name "+sam.getReadName()+"\n", Level.INFO); viewer.appendString("Coordinates "+sam.getAlignmentStart()+".."+ sam.getAlignmentEnd()+"\n", Level.DEBUG); if(sam.getReadGroup() != null) viewer.appendString("Read Group "+sam.getReadGroup().getId()+"\n", Level.DEBUG); viewer.appendString("Length "+sam.getReadLength()+"\n", Level.DEBUG); viewer.appendString("Reference Name "+sam.getReferenceName()+"\n", Level.DEBUG); viewer.appendString("Inferred Size "+sam.getInferredInsertSize()+"\n", Level.DEBUG); viewer.appendString("Mapping Quality "+sam.getMappingQuality()+"\n", Level.DEBUG); viewer.appendString("Cigar String "+sam.getCigarString()+"\n", Level.DEBUG); viewer.appendString("Strand "+ (sam.getReadNegativeStrandFlag() ? "-\n\n" : "+\n\n"), Level.DEBUG); if(sam.getReadPairedFlag()) { if(mate != null) { viewer.appendString("Mate Coordinates "+mate.getAlignmentStart()+".."+ mate.getAlignmentEnd()+"\n", Level.DEBUG); viewer.appendString("Mate Length "+mate.getReadLength()+"\n", Level.DEBUG); viewer.appendString("Mate Reference Name "+mate.getReferenceName()+"\n", Level.DEBUG); viewer.appendString("Mate Inferred Size "+mate.getInferredInsertSize()+"\n", Level.DEBUG); viewer.appendString("Mate Mapping Quality "+mate.getMappingQuality()+"\n", Level.DEBUG); viewer.appendString("Mate Cigar String "+mate.getCigarString()+"\n", Level.DEBUG); } else { viewer.appendString("Mate Start Coordinate "+sam.getMateAlignmentStart()+"\n", Level.DEBUG); viewer.appendString("Mate Reference Name "+sam.getMateReferenceName()+"\n", Level.DEBUG); } viewer.appendString("Mate Strand "+ (sam.getMateNegativeStrandFlag() ? "-" : "+"), Level.DEBUG); } viewer.appendString("\n\nFlags:", Level.INFO); viewer.appendString("\nDuplicate Read "+ (sam.getDuplicateReadFlag() ? "yes" : "no"), Level.DEBUG); viewer.appendString("\nSecondary Alignment "+ (sam.isSecondaryAlignment() ? "yes" : "no"), Level.DEBUG); viewer.appendString("\nSupplementary Alignment "+ (sam.getSupplementaryAlignmentFlag() ? "yes" : "no"), Level.DEBUG); viewer.appendString("\nRead Paired "+ (sam.getReadPairedFlag() ? "yes" : "no"), Level.DEBUG); if(sam.getReadPairedFlag()) { viewer.appendString("\nFirst of Pair "+ (sam.getFirstOfPairFlag() ? "yes" : "no"), Level.DEBUG); viewer.appendString("\nMate Unmapped "+ (sam.getMateUnmappedFlag() ? "yes" : "no"), Level.DEBUG); viewer.appendString("\nProper Pair "+ (sam.getProperPairFlag() ? "yes" : "no"), Level.DEBUG); } viewer.appendString("\nRead Fails Vendor\nQuality Check "+ (sam.getReadFailsVendorQualityCheckFlag() ? "yes" : "no"), Level.DEBUG); viewer.appendString("\nRead Unmapped "+ (sam.getReadUnmappedFlag() ? "yes" : "no"), Level.DEBUG); if(sam.getReadPairedFlag()) viewer.appendString("\nSecond Of Pair "+ (sam.getSecondOfPairFlag() ? "yes" : "no"), Level.DEBUG); viewer.appendString("\n\nRead Bases:\n", Level.INFO); wrapReadBases(sam, viewer); if(sam.getReadPairedFlag() && mate != null) { viewer.appendString("\nMate Read Bases:\n", Level.INFO); wrapReadBases(mate, viewer); } } private static void wrapReadBases(final SAMRecord sam, final FileViewer viewer) { final String seq = new String(sam.getReadBases()); final int MAX_SEQ_LINE_LENGTH = 100; for(int i=0; i<=seq.length(); i+=MAX_SEQ_LINE_LENGTH) { int iend = i+MAX_SEQ_LINE_LENGTH; if(iend > seq.length()) iend = seq.length(); viewer.appendString(seq.substring(i, iend)+"\n", Level.DEBUG); } } /** * Query for the mate of a read * @param mate * @return */ protected SAMRecord getMate(BamViewRecord thisSAMRecord) { if(!thisSAMRecord.sam.getReadPairedFlag()) // read is not paired in sequencing return null; SAMRecord mate = null; try { short fileIndex = 0; if(bamList.size()>1 && thisSAMRecord.bamIndex > 0) fileIndex = thisSAMRecord.bamIndex; String bam = bamList.get(fileIndex); final SamReader inputSam = getSAMFileReader(bam); mate = inputSam.queryMate(thisSAMRecord.sam); } catch (Exception e) { logger4j.warn("WARN: BamView.getMate() :: "+e.getMessage()); e.printStackTrace(); } return mate; } protected SAMRecordPredicate getSamRecordFlagPredicate() { return samRecordFlagPredicate; } protected void setSamRecordFlagPredicate( SAMRecordPredicate samRecordFlagPredicate) { laststart = -1; lastend = -1; this.samRecordFlagPredicate = samRecordFlagPredicate; } protected SAMRecordMapQPredicate getSamRecordMapQPredicate() { return samRecordMapQPredicate; } protected void setSamRecordMapQPredicate( SAMRecordMapQPredicate samRecordMapQPredicate) { laststart = -1; lastend = -1; this.samRecordMapQPredicate = samRecordMapQPredicate; } /** * @return the concatSequences */ protected boolean isConcatSequences() { return concatSequences; } /** * Check whether the given BAM record is highlighted currently. * @param bamRec BamViewRecord * @return boolean - true if highlighted */ protected boolean isThisBamRecordHighlighted(BamViewRecord bamRec) { if (highlightSAMRecord == null) { return false; } return BamUtils.samRecordEqualityCheck(highlightSAMRecord.sam, bamRec.sam); } class PairedRead { BamViewRecord sam1; BamViewRecord sam2; } class CreateFeatures { CreateFeatures(final GroupBamFrame groupsFrame) { final TextFieldInt threshold = new TextFieldInt(); final TextFieldInt minSize = new TextFieldInt(); final TextFieldInt minBams = new TextFieldInt(); threshold.setValue(6); minSize.setValue(6); minBams.setValue( (groupsFrame.getNumberOfGroups() == 1 ? bamList.size() : groupsFrame.getMaximumBamsInGroup()) ); final JPanel gridPanel = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.HORIZONTAL; c.gridx = 0; c.gridy = 0; gridPanel.add(new JLabel("Minimum number of reads:"), c); c.gridy++; gridPanel.add(threshold, c); c.gridy++; gridPanel.add(new JSeparator(), c); c.gridy++; gridPanel.add(new JLabel("Minimum number of BAMs/CRAMs for reads to be present in:"), c); c.gridy++; gridPanel.add(minBams, c); JRadioButton useAllBams = new JRadioButton("out of all BAMs/CRAMs", (groupsFrame.getNumberOfGroups() == 1)); JRadioButton useGroup = new JRadioButton("within a group", (groupsFrame.getNumberOfGroups() != 1)); if(groupsFrame.getNumberOfGroups() == 1) useGroup.setEnabled(false); final ButtonGroup group = new ButtonGroup(); group.add(useAllBams); group.add(useGroup); final Box xBox = Box.createHorizontalBox(); xBox.add(useAllBams); xBox.add(useGroup); xBox.add(Box.createHorizontalGlue()); c.gridy++; gridPanel.add(xBox, c); c.gridy++; gridPanel.add(new JSeparator(), c); c.gridy++; gridPanel.add(new JLabel("Minimum feature size:"), c); c.gridy++; gridPanel.add(minSize, c); final JCheckBox cbOpposite = new JCheckBox("Assume reads on opposite strand", false); cbOpposite.setToolTipText("for cDNA experiments when the reads are on the opposite strand"); c.gridy++; gridPanel.add(cbOpposite, c); int status = JOptionPane.showConfirmDialog(feature_display, gridPanel, "Options", JOptionPane.OK_CANCEL_OPTION); if(status == JOptionPane.CANCEL_OPTION) return; if(!useGroup.isSelected() && minBams.getValue() > bamList.size()) { status = JOptionPane.showConfirmDialog(feature_display, "The minimum number of BAMs setting can not be\n"+ "greater than the total number of BAM files.\n"+ "Set this to the number of BAMs (i.e. "+bamList.size()+").", "Options", JOptionPane.OK_CANCEL_OPTION); if(status == JOptionPane.CANCEL_OPTION) return; minBams.setValue(bamList.size()); } else if(useGroup.isSelected() && minBams.getValue() > groupsFrame.getMaximumBamsInGroup()) { status = JOptionPane.showConfirmDialog(feature_display, "Minimum number of BAMs setting can not be greater than\n"+ "the total number of BAM files found in any of the groups.\n"+ "Set this to the greatest number of BAM files in any\n"+ "group (i.e. "+groupsFrame.getMaximumBamsInGroup()+").", "Options", JOptionPane.OK_CANCEL_OPTION); if(status == JOptionPane.CANCEL_OPTION) return; minBams.setValue(groupsFrame.getMaximumBamsInGroup()); } new MappedReads(BamView.this, (useGroup.isSelected() ? groupsFrame : null), threshold.getValue(), minSize.getValue(), minBams.getValue(), cbOpposite.isSelected(), true); } } /** * Is BamView being used as a standalone application. * @return boolean */ public static boolean isStandaloneMode() { return standaloneMode; } /** * Set whether or not BamView is being used as a standalone application. * @param inStandaloneMode boolean */ public static void setStandaloneMode(boolean inStandaloneMode) { standaloneMode = inStandaloneMode; } /** * Close the application. */ public void close() { BamView.this.setVisible(false); Component comp = BamView.this; while( !(comp instanceof JFrame) ) comp = comp.getParent(); ((JFrame)comp).dispose(); } /** * Close the BAM panel. */ public void closeBamPanel() { BamView.this.getRootPane().setCursor(cdone); final JPanel containerPanel = (JPanel) mainPanel.getParent(); if (feature_display != null) { feature_display.removeDisplayAdjustmentListener(BamView.this); feature_display.getSelection().removeSelectionChangeListener(BamView.this); } containerPanel.remove(mainPanel); if(containerPanel.getComponentCount() > 0) containerPanel.revalidate(); else { if(entry_edit != null) entry_edit.setNGDivider(); else containerPanel.setVisible(false); } } /** * Called when we encounter a major error while loading a BAM or CRAM file. * It will display the errors in an option pane and then close the panel, * exiting completely of BamView is in standalone mode. * @param errorText String - the error message to display. */ protected void handleFatalError(final String errorText) { if (!foundFatalErrors.get()) { foundFatalErrors.set(true); logger4j.error("BamView errors: " + errorText); SwingUtilities.invokeLater(new Runnable() { public void run() { JOptionPane.showMessageDialog(null, errorText); readsInView.clear(); closeBamPanel(); if (BamView.isStandaloneMode()) { System.exit(0); } } }); } } /** * Class main method. * @param args */ public static void main(String[] args) { BamFrame frame = new BamFrame(); if(args.length == 0 && BamFrame.isMac()) { try { Thread.sleep(1000); } catch (InterruptedException e1) {} if(frame.getBamFile() != null) args = new String[]{ frame.getBamFile() }; } List alignmentFileList = new Vector(); String reference = null; if(args.length == 0 || args[0].equals("NEW-BAMVIEW")) { System.setProperty("default_directory", System.getProperty("user.dir")); FileSelectionDialog fileSelection = new FileSelectionDialog( null, true, "BamView", "BAM/CRAM"); alignmentFileList = fileSelection.getFiles(BAM_SUFFIX); reference = fileSelection.getReferenceFile(); if(reference == null || reference.trim().equals("")) reference = null; if(alignmentFileList == null || alignmentFileList.size() < 1) { if(args.length > 0 && args[0].equals("NEW-BAMVIEW")) return; System.err.println("No files found."); System.exit(0); } } else if(!args[0].startsWith("-")) { for(int i=0; i< args.length; i++) alignmentFileList.add(args[i]); } int nbasesInView = 2000; String chr = null; String vw = null; boolean orientation = false; boolean covPlot = false; boolean snpPlot = false; int base = 0; for(int i=0;i 0) view.scrollBar.setValue(base); if(orientation) view.isOrientation = true; if(covPlot) { view.isCoverage = true; view.coveragePanel.setVisible(true); } if(snpPlot) { view.isSNPplot = true; view.snpPanel.setVisible(true); } // translucent //frame.getRootPane().putClientProperty("Window.alpha", new Float(0.9f)); /*frame.addWindowFocusListener(new WindowFocusListener() { public void windowGainedFocus(WindowEvent e) { view.requestFocus(); } public void windowLostFocus(WindowEvent e){} });*/ frame.pack(); view.jspView.getVerticalScrollBar().setValue( view.jspView.getVerticalScrollBar().getMaximum()); frame.setVisible(true); } }