diff --git a/uk/ac/sanger/artemis/components/alignment/JamView.java b/uk/ac/sanger/artemis/components/alignment/JamView.java new file mode 100644 index 0000000000000000000000000000000000000000..e73eaf3171c5020bd8b485f0098686d824e6c81b --- /dev/null +++ b/uk/ac/sanger/artemis/components/alignment/JamView.java @@ -0,0 +1,473 @@ + +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.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +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.io.BufferedReader; +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.JPanel; +import javax.swing.JScrollPane; + +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 +{ + 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 String sam; + private EntryGroup entryGroup; + private JScrollPane jspView; + private JComboBox combo; + private int nbasesInView; + + 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(); + } + + private void readHeader() + { + String cmd[] = { "/Users/tjc/BAM/samtools-0.1.5c/samtools", + "view", "-H", bam }; + + RunSamTools samtools = new RunSamTools(cmd, 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(); + } + } + + private void readFromBam(int start, int end) + { + String refName = (String) combo.getSelectedItem(); + String cmd[] = { "/Users/tjc/BAM/samtools-0.1.5c/samtools", + "view", bam, refName+":"+start+"-"+end }; + + for(int i=0; i<cmd.length;i++) + System.out.print(cmd[i]+" "); + System.out.println(); + RunSamTools samtools = new RunSamTools(cmd, null, null); + + if(samtools.getProcessStderr() != null) + System.out.println(samtools.getProcessStderr()); + + sam = samtools.getProcessStdout(); + + StringReader samReader = new StringReader(sam); + BufferedReader buff = new BufferedReader(samReader); + String line; + try + { + if(readsInView == null) + readsInView = new Vector<Read>(); + else + readsInView.clear(); + while((line = buff.readLine()) != null) + { + String fields[] = line.split("\t"); + + Read pread = new Read(); + pread.qname = fields[0]; + pread.flag = Integer.parseInt(fields[1]); + pread.rname = fields[2]; + pread.pos = Integer.parseInt(fields[3]); + pread.mapq = Short.parseShort(fields[4]); + pread.mpos = Integer.parseInt(fields[7]); + pread.isize = Integer.parseInt(fields[8]); + pread.seq = fields[9]; + readsInView.add(pread); + } + + Collections.sort(readsInView, new ReadComparator()); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + /** + * 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 * ( x / getWidth())); + int end = (int) (start + (jspView.getViewport().getWidth() / pixPerBase)); + + int scaleHeight = 15; + readFromBam(start, end); + + drawScale(g2, start, end, pixPerBase); + + Stroke originalStroke = g2.getStroke(); + Stroke stroke = + new BasicStroke (2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); + + 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 int getBaseAtStartOfView() + { + String refName = (String) combo.getSelectedItem(); + int seqLength = seqLengths.get(refName); + double x = jspView.getViewport().getViewRect().getX(); + return (int) (seqLength * ( x / getWidth())); + } + + 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); + } + + private void drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division) + { + int markStart = (Math.round(start/division)*division)+division; + 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); + } + } + + private int drawRead(Graphics2D g2, Read read, + float pixPerBase, Stroke stroke, int ypos) + { + int thisStart = (int) (read.pos*pixPerBase); + int thisEnd = (int) (thisStart + (read.seq.length()*pixPerBase)); + g2.setStroke(stroke); + g2.drawLine(thisStart, ypos, thisEnd, ypos); + return thisStart; + } + + + + + public void showJFrame() + { + this.nbasesInView = nbasesInView; + JFrame frame = new JFrame("JamTool"); + + combo = new JComboBox(seqNames); + combo.setEditable(false); + combo.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent e) + { + setZoomLevel(JamView.this.nbasesInView); + } + }); + + frame.setPreferredSize(new Dimension(1000,800)); + setLength(nbasesInView); + + jspView = new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_NEVER, + JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + + JPanel panel = (JPanel) frame.getContentPane(); + + panel.setLayout(new BorderLayout()); + panel.add(combo, BorderLayout.NORTH); + panel.add(jspView, BorderLayout.CENTER); + frame.pack(); + + setFocusable(true); + requestFocusInWindow(); + + addKeyListener(new KeyAdapter() + { + public void keyPressed(final KeyEvent event) + { + switch(event.getKeyCode()) + { + case KeyEvent.VK_UP: + setZoomLevel( (int) (JamView.this.nbasesInView*1.1) ); + repaint(); + break; + case KeyEvent.VK_DOWN: + setZoomLevel( (int) (JamView.this.nbasesInView*.9) ); + repaint(); + break; + default: + break; + } + } + }); + + addFocusListener(new FocusListener() + { + public void focusGained(FocusEvent fe) {} + public void focusLost(FocusEvent fe) {} + }); + + frame.setVisible(true); + } + + private void setZoomLevel(final int nbasesInView) + { + int startBase = getBaseAtStartOfView(); + + this.nbasesInView = nbasesInView; + setLength(this.nbasesInView); + revalidate(); + repaint(); + System.out.println(startBase+" "+getBaseAtStartOfView()); + + String refName = (String) combo.getSelectedItem(); + int seqLength = seqLengths.get(refName); + + + float pixPerBase = ((float)getWidth())/(float)(seqLength); + Point p = jspView.getViewport().getViewPosition(); + p.x = (int)(startBase*pixPerBase); + jspView.getViewport().setViewPosition(p); + revalidate(); + repaint(); + + System.out.println(startBase+" "+getBaseAtStartOfView()); + } + + private void setLength(int basesToShow) + { + String refName = (String) combo.getSelectedItem(); + int seqLength = seqLengths.get(refName); + float pixPerBase = 1000.f/(float)(basesToShow); + setPreferredSize( new Dimension((int) (seqLength*pixPerBase), + getSize().height) ); + } + + /** + * 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 Read + { + String qname; // query name + int flag; // bitwise flag + String rname; // reference name + int pos; // leftmost coordinate + short mapq; // MAPping Quality + String cigar; // extended CIGAR string + String mrnm; // Mate Reference sequence NaMe; Ò=Ó if the same as rname + int mpos; // leftmost Mate POSition + int isize; // inferred Insert SIZE + String seq; // query SEQuence; Ò=Ó for a match to the reference; n/N/. for ambiguity + } + + 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 static void main(String[] args) + { + String bam = args[0]; + int nbasesInView = Integer.parseInt(args[1]); + String reference = null; + if(args.length > 2) + reference = args[2]; + + JamView view = new JamView(bam, reference, nbasesInView); + view.showJFrame(); + } +} diff --git a/uk/ac/sanger/artemis/components/alignment/RunSamTools.java b/uk/ac/sanger/artemis/components/alignment/RunSamTools.java new file mode 100644 index 0000000000000000000000000000000000000000..2ac1284d4116bb25aee4456eaa61a74cd737fdd3 --- /dev/null +++ b/uk/ac/sanger/artemis/components/alignment/RunSamTools.java @@ -0,0 +1,317 @@ + +package uk.ac.sanger.artemis.components.alignment; + +import java.io.*; +import javax.swing.JTextArea; + +/** +* Used to run an samtools process this reads stdout and +* stderr in separate threads. +*/ +public class RunSamTools +{ + + /** running process */ + private Process p; + /** standard out */ + private StringBuffer stdout = new StringBuffer(); + /** standard error */ + private StringBuffer stderr = new StringBuffer(); + + private String initialIOError = null; + + /** running directory */ + //private File project; + /** process status */ + private String status; + private StdoutHandler stdouth; + private StderrHandler stderrh; + private JTextArea textArea; + + + /** + * @param cmd command to run + * @param env environment + * @param project running directory + */ + public RunSamTools(String cmd[], + String[] envp, File project) + { + //this.project = project; + status = "0"; + + Runtime run = Runtime.getRuntime(); + try + { + p = run.exec(cmd,envp,project); + + // 2 threads to read in stdout & stderr buffers + // to prevent blocking + stdouth = new StdoutHandler(this); + stderrh = new StderrHandler(this); + stdouth.start(); + stderrh.start(); + } + catch(IOException ioe) + { + System.err.println("Error executing: "+ + cmd[0]); + initialIOError = ioe.getMessage(); + status = "1"; + } + } + + /** + * Read in the process stderr. + */ + private void readProcessStderr() + { + BufferedInputStream stderrStream = null; + BufferedReader stderrRead = null; + try + { + //String line; + stderrStream = + new BufferedInputStream(p.getErrorStream()); + stderrRead = + new BufferedReader(new InputStreamReader(stderrStream)); + char c[] = new char[100]; + int nc = 0; + + while((nc = stderrRead.read(c,0,100)) != -1) + stderr = stderr.append(new String(c,0,nc)); + } + catch (IOException io) + { + System.err.println("RunEmbossApplication2: Error in "+ + "collecting standard out"); + } + finally + { + try + { + if(stderrStream!=null) + stderrStream.close(); + } + catch(IOException ioe) + { + System.err.println("RunEmbossApplication2: Error closing stream"); + } + try + { + if(stderrRead!=null) + stderrRead.close(); + } + catch(IOException ioe) + { + System.err.println("RunEmbossApplication2: Error closing reader"); + } + } + + return; + } + + /** + * Read in the process stdout. + */ + private void readProcessStdout() + { + BufferedInputStream stdoutStream = null; + BufferedReader stdoutRead = null; + try + { + //String line; + stdoutStream = + new BufferedInputStream(p.getInputStream()); + stdoutRead = + new BufferedReader(new InputStreamReader(stdoutStream)); + + char c[] = new char[200]; + int nc = 0; + String chunk; + + while((nc = stdoutRead.read(c,0,200)) != -1) + { + chunk = new String(c,0,nc); + stdout = stdout.append(chunk); + if(textArea != null) + { + textArea.append(chunk); + textArea.setCaretPosition(textArea.getDocument().getLength()); + } + } + } + catch (IOException io) + { + System.err.println("RunEmbossApplication2: Error in "+ + "collecting standard out"); + } + finally + { + try + { + if(stdoutStream!=null) + stdoutStream.close(); + } + catch(IOException ioe) + { + System.err.println("RunEmbossApplication2: Error closing stream"); + } + try + { + if(stdoutRead!=null) + stdoutRead.close(); + } + catch(IOException ioe) + { + System.err.println("RunEmbossApplication2: Error closing reader"); + } + } + + return; + } + + /** + * This method can be called after the process has completed + * to write the stdout to the project directory. + */ +// private void writeStdout() +// { +// if(project != null) +// { +// PrintWriter out = null; +// String fname = ""; +// try +// { +// fname = project.getCanonicalPath() + +// "/application_stdout"; +// File so = new File(fname); +// +// if(!so.exists()) +// so.createNewFile(); +// +// out = new PrintWriter(new FileWriter(fname,true)); +// out.println(stdout); +// } +// catch(IOException ioe) +// { +// System.err.println("RunEmbossApplication2: Error writing" + +// fname); +// } +// finally +// { +// if(out!=null) +// out.close(); +// } +// } +// } + + /** + * @return standard out + */ + public String getProcessStdout() + { + try + { + // make sure we hang around for stdout + while(stdouth.isAlive()) + Thread.sleep(10); + } + catch(InterruptedException ie) + { + ie.printStackTrace(); + } + + return stdout.toString(); + } + + /** + * @return stderr + */ + public String getProcessStderr() + { + try + { + // make sure we hang around for stdout + while(stderrh.isAlive()) + Thread.sleep(10); + } + catch(InterruptedException ie) + { + ie.printStackTrace(); + } + + return new String(stderr.toString().trim()); + } + + /** + * Wait for the process to end + */ + public void waitFor() + { + try + { + int exitVal = p.waitFor(); + + if(exitVal != 0) + System.out.println("Exit value:: "+exitVal); + } + catch(InterruptedException ie) + { + ie.printStackTrace(); + } + } + + /** + * @return process + */ + public Process getProcess() + { + return p; + } + + /** + * @return status + */ + public String getStatus() + { + return status; + } + + class StdoutHandler extends Thread + { + RunSamTools rea; + + protected StdoutHandler(RunSamTools rea) + { + this.rea = rea; + } + + public void run() + { + rea.readProcessStdout(); + } + } + + class StderrHandler extends Thread + { + RunSamTools rea; + + protected StderrHandler(RunSamTools rea) + { + this.rea = rea; + } + + public void run() + { + rea.readProcessStderr(); + } + } + + public String getInitialIOError() + { + return initialIOError; + } + +} +