-
tjc authored
git-svn-id: svn+ssh://svn.internal.sanger.ac.uk/repos/svn/pathsoft/artemis/trunk@11654 ee4ac58c-ac51-4696-9907-e4b3aa274f04
tjc authoredgit-svn-id: svn+ssh://svn.internal.sanger.ac.uk/repos/svn/pathsoft/artemis/trunk@11654 ee4ac58c-ac51-4696-9907-e4b3aa274f04
JamView.java 20.45 KiB
/* 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.
*
*/
package uk.ac.sanger.artemis.components.alignment;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
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.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import uk.ac.sanger.artemis.Entry;
import uk.ac.sanger.artemis.EntryGroup;
import uk.ac.sanger.artemis.Options;
import uk.ac.sanger.artemis.SimpleEntryGroup;
import uk.ac.sanger.artemis.components.EntryFileDialog;
import uk.ac.sanger.artemis.components.MessageDialog;
import uk.ac.sanger.artemis.io.EntryInformation;
import uk.ac.sanger.artemis.sequence.Bases;
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 JamView extends JPanel
implements Scrollable
{
private static final long serialVersionUID = 1L;
private List<Read> readsInView;
private Hashtable<String, Integer> seqLengths = new Hashtable<String, Integer>();
private Vector<String> seqNames = new Vector<String>();
private String bam;
private EntryGroup entryGroup;
private JScrollPane jspView;
private JComboBox combo;
private int nbasesInView;
private int laststart;
private int lastend;
private int maxUnitIncrement = 4;
private int ALIGNMENT_PIX_PER_BASE;
public JamView(String bam,
String reference,
int nbasesInView)
{
super();
setBackground(Color.white);
this.bam = bam;
this.nbasesInView = nbasesInView;
if(reference != null)
{
entryGroup = new SimpleEntryGroup();
try
{
getEntry(reference,entryGroup);
}
catch (NoSequenceException e)
{
e.printStackTrace();
}
}
readHeader();
// set font size
setFont(getFont().deriveFont(12.f));
final javax.swing.plaf.FontUIResource font_ui_resource =
new javax.swing.plaf.FontUIResource(getFont());
// Options.getOptions().getFontUIResource();
java.util.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);
}
FontMetrics fm = getFontMetrics(getFont());
ALIGNMENT_PIX_PER_BASE = (int) (fm.stringWidth("A")*1.1);
System.out.println("Font size "+getFont().getSize());
}
/**
* Read the BAM/SAM header
*/
private void readHeader()
{
String samtoolCmd = "";
if(System.getProperty("samtoolDir") != null)
samtoolCmd = System.getProperty("samtoolDir");
String cmd[] = { samtoolCmd+File.separator+"samtools",
"view", "-H", bam };
RunSamTools samtools = new RunSamTools(cmd, null, null, null);
if(samtools.getProcessStderr() != null)
System.out.println(samtools.getProcessStderr());
String header = samtools.getProcessStdout();
StringReader samReader = new StringReader(header);
BufferedReader buff = new BufferedReader(samReader);
String line;
try
{
while((line = buff.readLine()) != null)
{
if(line.indexOf("LN:") > -1)
{
String parts[] = line.split("\t");
String name = "";
int seqLength = 0;
for(int i=0; i<parts.length; i++)
{
if(parts[i].startsWith("LN:"))
seqLength = Integer.parseInt( parts[i].substring(3) );
else if(parts[i].startsWith("SN:"))
name = parts[i].substring(3);
}
seqLengths.put(name, seqLength);
seqNames.add(name);
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
/**
* Read data from BAM/SAM file for a region.
* @param start
* @param end
* @param pair_sort
*/
private void readFromBam(int start, int end)
{
String refName = (String) combo.getSelectedItem();
String samtoolCmd = "";
if(System.getProperty("samtoolDir") != null)
samtoolCmd = System.getProperty("samtoolDir");
String cmd[] = { samtoolCmd+File.separator+"samtools",
"view", bam, refName+":"+start+"-"+end };
for(int i=0; i<cmd.length;i++)
System.out.print(cmd[i]+" ");
System.out.println();
if(readsInView == null)
readsInView = new Vector<Read>();
else
readsInView.clear();
RunSamTools samtools = new RunSamTools(cmd, null, null, readsInView);
if(samtools.getProcessStderr() != null)
System.out.println(samtools.getProcessStderr());
samtools.waitForStdout();
}
/**
* Override
*/
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
String refName = (String) combo.getSelectedItem();
int seqLength = seqLengths.get(refName);
float pixPerBase = ((float)getWidth())/(float)(seqLength);
double x = jspView.getViewport().getViewRect().getX();
int start = (int) (seqLength * ( (float)x / (float)getWidth()));
int end = (int) (start + ((float)jspView.getViewport().getWidth() /
(float)pixPerBase));
if(laststart != start ||
lastend != end)
{
try
{
readFromBam(start, end);
if(pixPerBase < ALIGNMENT_PIX_PER_BASE)
Collections.sort(readsInView, new ReadComparator());
}
catch(OutOfMemoryError ome)
{
JOptionPane.showMessageDialog(this, "Out of Memory");
return;
}
}
laststart = start;
lastend = end;
if(pixPerBase >= ALIGNMENT_PIX_PER_BASE)
drawBaseAlignment(g2, seqLength, pixPerBase, start, end);
else
drawLineView(g2, seqLength, pixPerBase, start, end);
}
private void drawBaseAlignment(Graphics2D g2, int seqLength,
float pixPerBase, int start, int end)
{
FontMetrics fm = getFontMetrics(getFont());
int ypos = fm.getHeight();
drawBaseScale(g2, start, end, ypos);
boolean draw[] = new boolean[readsInView.size()];
for(int i=0; i<readsInView.size(); i++)
draw[i] = false;
ypos+=6;
for(int i=0; i<readsInView.size(); i++)
{
if (!draw[i])
{
Read thisRead = readsInView.get(i);
ypos+=10;
drawSequence(g2, thisRead, pixPerBase, ypos);
draw[i] = true;
int thisEnd = thisRead.pos+thisRead.seq.length();
for(int j=i+1; j<readsInView.size(); j++)
{
if (!draw[j])
{
Read nextRead = readsInView.get(j);
if(nextRead.pos > thisEnd+1)
{
drawSequence(g2, nextRead, pixPerBase, ypos);
draw[j] = true;
thisEnd = nextRead.pos+nextRead.seq.length();
}
}
}
}
}
if(ypos > getHeight())
{
setPreferredSize(new Dimension(getWidth(), ypos));
revalidate();
}
}
/**
* Draw the query sequence
* @param g2
* @param read
* @param pixPerBase
* @param ypos
*/
private void drawSequence(Graphics2D g2, Read read, float pixPerBase, int ypos)
{
if ((read.flag & 0x0001) != 0x0001 ||
(read.flag & 0x0008) == 0x0008)
g2.setColor(Color.black);
else
g2.setColor(Color.blue);
int xpos;
for(int i=0;i<read.seq.length(); i++)
{
xpos = ((read.pos-1) + i)*ALIGNMENT_PIX_PER_BASE;
g2.drawString(read.seq.substring(i, i+1), xpos, ypos);
}
}
private void drawLineView(Graphics2D g2, int seqLength, float pixPerBase, int start, int end)
{
drawScale(g2, start, end, pixPerBase);
Stroke originalStroke = g2.getStroke();
Stroke stroke =
new BasicStroke (2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
int scaleHeight = 15;
for(int i=0; i<readsInView.size(); i++)
{
Read thisRead = readsInView.get(i);
Read nextRead = null;
if( (thisRead.flag & 0x0001) != 0x0001 ||
(thisRead.flag & 0x0008) == 0x0008 ) // not single read
continue;
if(i < readsInView.size()-1)
{
nextRead = readsInView.get(i+1);
i++;
}
if(nextRead != null &&
(nextRead.flag & 0x0001) == 0x0001 &&
(nextRead.flag & 0x0008) != 0x0008 )
{
if(thisRead.qname.equals(nextRead.qname))
{
if( (thisRead.flag & 0x0010) == 0x0010 &&
(nextRead.flag & 0x0010) == 0x0010 )
g2.setColor(Color.red);
else
g2.setColor(Color.blue);
int ypos = (getHeight() - scaleHeight) - ( Math.abs(thisRead.isize) );
int thisStart = drawRead(g2, thisRead, pixPerBase, stroke, ypos);
int nextStart = drawRead(g2, nextRead, pixPerBase, stroke, ypos);
g2.setStroke(originalStroke);
g2.setColor(Color.LIGHT_GRAY);
g2.drawLine(thisStart, ypos, nextStart, ypos);
}
else
i--;
}
}
}
private void drawBaseScale(Graphics2D g2, int start, int end, int ypos)
{
int startMark = (((int)(start/10))*10)+1;
for(int i=startMark; i<end; i+=10)
{
int xpos = (i-1)*ALIGNMENT_PIX_PER_BASE;
g2.drawString(Integer.toString(i), xpos, ypos);
xpos+=(ALIGNMENT_PIX_PER_BASE/2);
g2.drawLine(xpos, ypos+1, xpos, ypos+5);
}
}
private void drawScale(Graphics2D g2, int start, int end, float pixPerBase)
{
g2.setColor(Color.black);
g2.drawLine( (int)(start*pixPerBase), getHeight()-14,
(int)(end*pixPerBase), getHeight()-14);
int interval = end-start;
if(interval > 256000)
drawTicks(g2, start, end, pixPerBase, 512000);
else if(interval > 64000)
drawTicks(g2, start, end, pixPerBase, 12800);
else if(interval > 16000)
drawTicks(g2, start, end, pixPerBase, 3200);
else if(interval > 4000)
drawTicks(g2, start, end, pixPerBase, 800);
else if(interval > 1000)
drawTicks(g2, start, end, pixPerBase, 200);
else
drawTicks(g2, start, end, pixPerBase, 50);
}
private void drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division)
{
int markStart = (Math.round(start/division)*division);
if(markStart < 1)
markStart = 1;
int sm = markStart-(division/2);
if(sm > start)
g2.drawLine((int)(sm*pixPerBase), getHeight()-14,(int)(sm*pixPerBase), getHeight()-12);
for(int m=markStart; m<end; m+=division)
{
g2.drawString(Integer.toString(m), m*pixPerBase, getHeight()-1);
g2.drawLine((int)(m*pixPerBase), getHeight()-14,(int)(m*pixPerBase), getHeight()-11);
sm = m+(division/2);
if(sm < end)
g2.drawLine((int)(sm*pixPerBase), getHeight()-14,(int)(sm*pixPerBase), getHeight()-12);
if(m == 1)
m = 0;
}
}
private int drawRead(Graphics2D g2, Read read,
float pixPerBase, Stroke stroke, int ypos)
{
int thisStart = (int) ((read.pos-1)*pixPerBase);
int thisEnd = (int) (thisStart + (read.seq.length()*pixPerBase));
g2.setStroke(stroke);
g2.drawLine(thisStart, ypos, thisEnd, ypos);
return thisStart;
}
public void addToPanel(final JPanel panel)
{
JPanel topPanel = new JPanel(new GridBagLayout());
GridBagConstraints gc = new GridBagConstraints();
combo = new JComboBox(seqNames);
combo.setEditable(false);
combo.addItemListener(new ItemListener()
{
public void itemStateChanged(ItemEvent e)
{
laststart = -1;
lastend = -1;
setZoomLevel(JamView.this.nbasesInView);
}
});
gc.fill = GridBagConstraints.NONE;
gc.anchor = GridBagConstraints.FIRST_LINE_START;
topPanel.add(combo, gc);
panel.setPreferredSize(new Dimension(1000,500));
setLength(nbasesInView);
jspView = new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
panel.setLayout(new BorderLayout());
panel.add(topPanel, BorderLayout.NORTH);
panel.add(jspView, BorderLayout.CENTER);
jspView.getVerticalScrollBar().setValue(
jspView.getVerticalScrollBar().getMaximum());
setFocusable(true);
requestFocusInWindow();
addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
JamView.this.requestFocus();
}
});
addKeyListener(new KeyAdapter()
{
public void keyPressed(final KeyEvent event)
{
switch(event.getKeyCode())
{
case KeyEvent.VK_UP:
int startBase = getBaseAtStartOfView();
setZoomLevel( (int) (JamView.this.nbasesInView*1.1) );
setViewToBasePosition(startBase);
repaint();
break;
case KeyEvent.VK_DOWN:
startBase = getBaseAtStartOfView();
setZoomLevel( (int) (JamView.this.nbasesInView*.9) );
setViewToBasePosition(startBase);
repaint();
break;
default:
break;
}
}
});
addFocusListener(new FocusListener()
{
public void focusGained(FocusEvent fe) {}
public void focusLost(FocusEvent fe) {}
});
}
private int getBaseAtStartOfView()
{
String refName = (String) combo.getSelectedItem();
int seqLength = seqLengths.get(refName);
double x = jspView.getViewport().getViewRect().getX();
return (int) (seqLength * ( x / getWidth()));
}
/**
* Set the panel size based on the number of bases visible
* and repaint.
* @param nbasesInView
*/
private void setZoomLevel(final int nbasesInView)
{
this.nbasesInView = nbasesInView;
setLength(this.nbasesInView);
revalidate();
repaint();
}
/**
* Set the ViewPort so it starts at the given base position.
* @param base
*/
private void setViewToBasePosition(int base)
{
Point p = jspView.getViewport().getViewPosition();
String refName = (String) combo.getSelectedItem();
int seqLength = seqLengths.get(refName);
p.x = (int) ((getPreferredSize().width)*(((float)base)/(float)seqLength));
jspView.getViewport().setViewPosition(p);
}
/**
* Set the panel size based on the number of bases visible.
* @param nbasesInView
*/
private void setLength(int basesToShow)
{
String refName = (String) combo.getSelectedItem();
int seqLength = seqLengths.get(refName);
double pixPerBase = 1000.d/(double)(basesToShow);
System.out.println(pixPerBase+" "+ALIGNMENT_PIX_PER_BASE);
if(pixPerBase > ALIGNMENT_PIX_PER_BASE)
{
pixPerBase = ALIGNMENT_PIX_PER_BASE;
jspView.getVerticalScrollBar().setValue(0);
}
Dimension d = new Dimension();
d.setSize((seqLength*pixPerBase), 800.d);
setPreferredSize(d);
}
/**
* 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
{
Bases bases = null;
if(entryGroup.getSequenceEntry() != null)
bases = entryGroup.getSequenceEntry().getBases();
if(bases == null)
entry = new Entry(new_embl_entry);
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;
}
class ReadComparator implements Comparator
{
public int compare(Object o1, Object o2)
{
Read pr1 = (Read) o1;
Read pr2 = (Read) o2;
return pr1.qname.compareTo(pr2.qname);
}
}
public Dimension getPreferredScrollableViewportSize()
{
return getPreferredSize();
}
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction)
{
if (orientation == SwingConstants.HORIZONTAL)
return visibleRect.width - maxUnitIncrement;
else
return visibleRect.height - maxUnitIncrement;
}
public boolean getScrollableTracksViewportHeight()
{
return false;
}
public boolean getScrollableTracksViewportWidth()
{
return false;
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation,
int direction)
{
//Get the current position.
int currentPosition = 0;
if (orientation == SwingConstants.HORIZONTAL)
currentPosition = visibleRect.x;
else
currentPosition = visibleRect.y;
//Return the number of pixels between currentPosition
//and the nearest tick mark in the indicated direction.
if (direction < 0)
{
int newPosition = currentPosition -
(currentPosition / maxUnitIncrement)
* maxUnitIncrement;
return (newPosition == 0) ? maxUnitIncrement : newPosition;
}
else
{
return ((currentPosition / maxUnitIncrement) + 1)
* maxUnitIncrement
- currentPosition;
}
}
public static void main(String[] args)
{
String bam = args[0];
int nbasesInView = 1000;
String reference = null;
for(int i=0;i<args.length; i++)
{
if(args[i].equals("-a"))
bam = args[++i];
else if(args[i].equals("-r"))
reference = args[++i];
else if(args[i].equals("-v"))
nbasesInView = Integer.parseInt(args[++i]);
else if(args[i].equals("-s"))
System.setProperty("samtoolDir", args[++i]);
else if(args[i].startsWith("-h"))
{
System.out.println("-h\t show help");
System.out.println("-a\t BAM/SAM file to display");
System.out.println("-r\t reference file (optional)");
System.out.println("-v\t number of bases to display in the view (optional)");
System.out.println("-s\t samtool directory");
System.exit(0);
}
}
JamView view = new JamView(bam, reference, nbasesInView);
JFrame frame = new JFrame("JAM");
view.addToPanel((JPanel)frame.getContentPane());
frame.pack();
frame.setVisible(true);
}
}