diff --git a/etc/versions b/etc/versions index 1f22df39c3260e74eeb38d988df1b0a385f712e1..4527d9a3057c34d1903200f5298863478f742e56 100644 --- a/etc/versions +++ b/etc/versions @@ -1,4 +1,4 @@ -Artemis Release 14.2.0 -ACT Release 11.2.0 +Artemis Release 14.2.1 +ACT Release 11.2.1 DNAPlotter Release 1.8 -BamView 1.2.5 +BamView 1.2.6 diff --git a/uk/ac/sanger/artemis/components/Plot.java b/uk/ac/sanger/artemis/components/Plot.java index 175832128117d16e0d85edf50e66e3b67e3f612a..9fcc723afc22fc2d14351ac2cb072652b0873014 100644 --- a/uk/ac/sanger/artemis/components/Plot.java +++ b/uk/ac/sanger/artemis/components/Plot.java @@ -938,8 +938,7 @@ public abstract class Plot extends JPanel int R = col.getRed(); int G = col.getGreen(); int B = col.getBlue(); - - float scale = ((float)(NUMBER_OF_SHADES-i) * (float)(255 / NUMBER_OF_SHADES )) ; + int scale = NUMBER_OF_SHADES-i; if((R+scale) <= 255) R += scale; diff --git a/uk/ac/sanger/artemis/components/alignment/BamView.java b/uk/ac/sanger/artemis/components/alignment/BamView.java index ca938c72d7d1b24bc0bf5e2212d81799faf0d407..654e724fc52d4f2544c550cbb043876cade1ea6f 100644 --- a/uk/ac/sanger/artemis/components/alignment/BamView.java +++ b/uk/ac/sanger/artemis/components/alignment/BamView.java @@ -216,6 +216,7 @@ public class BamView extends JPanel private AlphaComposite translucent = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f); + private GroupBamFrame groupsFrame = new GroupBamFrame(this, bamFilesMenu); private CoveragePanel coverageView = new CoveragePanel(); protected static String BAM_SUFFIX = ".*\\.(bam|cram)$"; @@ -372,6 +373,10 @@ public class BamView extends JPanel public String getToolTipText() { + if(isCoverageView(getPixPerBaseByWidth()) && lastMousePoint != null) + return coverageView.getToolTipText( + lastMousePoint.y-getJspView().getViewport().getViewPosition().y); + if(mouseOverSAMRecord == null) return null; @@ -677,7 +682,7 @@ public class BamView extends JPanel final int cov[] = new int[nbins]; for(int i=0; i<nbins; i++) cov[i] = 0; - + final CloseableIterator<SAMRecord> it = inputSam.queryOverlapping(refName, start, end); try { @@ -1809,7 +1814,7 @@ public class BamView extends JPanel } int hgt = jspView.getVisibleRect().height-scaleHeight; - if(!cbCoverageStrandView.isSelected() && !cbCoverageHeatMap.isSelected()) + if(!cbCoverageStrandView.isSelected() && !coverageView.isPlotHeatMap()) { try { @@ -1824,8 +1829,9 @@ public class BamView extends JPanel g2.translate(0, getJspView().getViewport().getViewPosition().y); coverageView.drawSelectionRange(g2, pixPerBase, start, end, getHeight(), Color.PINK); - coverageView.draw(g2, getWidth(), hgt); - coverageView.drawMax(g2); + coverageView.draw(g2, getWidth(), hgt, hideBamList); + if(!coverageView.isPlotHeatMap()) + coverageView.drawMax(g2); } /** @@ -2408,9 +2414,8 @@ public class BamView extends JPanel }); bamFilesMenu.setFont(addBam.getFont()); - + final JMenuItem groupBams = new JMenuItem("Group BAMs ..."); - final GroupBamFrame groupsFrame = new GroupBamFrame(this, bamFilesMenu); groupBams.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { @@ -3623,8 +3628,10 @@ public class BamView extends JPanel */ class PopupListener extends MouseAdapter { - JMenuItem gotoMateMenuItem; - JMenuItem showDetails; + private JMenuItem gotoMateMenuItem; + private JMenuItem showDetails; + private JMenu coverageMenu; + private JMenuItem createGroup; public void mouseClicked(MouseEvent e) { @@ -3637,7 +3644,13 @@ public class BamView extends JPanel if(e.getClickCount() > 1) getSelection().clear(); else if(e.getButton() == MouseEvent.BUTTON1) - highlightSAMRecord = mouseOverSAMRecord; + { + if(isCoverageView(getPixPerBaseByWidth())) + coverageView.singleClick(e.isShiftDown(), + e.getPoint().y-getJspView().getViewport().getViewPosition().y); + else + highlightSAMRecord = mouseOverSAMRecord; + } else highlightRange(e, MouseEvent.BUTTON2_DOWN_MASK); repaint(); @@ -3657,19 +3670,62 @@ public class BamView extends JPanel 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"); + createGroup.addActionListener(new ActionListener() + { + private int n = 1; + public void actionPerformed(ActionEvent e) + { + String groupName = "group_"+n; + groupsFrame.addGroup(groupName); + final List<String> 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() ) diff --git a/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java b/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java index 8942ad5e0f10097fd9a68965e2aaf71bde15d050..4810c54fbd65fc727e91a5c2e71feb95fa2ce1df 100644 --- a/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java +++ b/uk/ac/sanger/artemis/components/alignment/CoveragePanel.java @@ -25,17 +25,21 @@ package uk.ac.sanger.artemis.components.alignment; import java.awt.AlphaComposite; +import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; +import java.awt.Stroke; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.GeneralPath; +import java.io.File; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; +import java.util.Vector; import javax.swing.JCheckBox; import javax.swing.JComponent; @@ -60,9 +64,12 @@ import net.sf.samtools.SAMRecord; private static boolean redraw = false; private boolean setMaxBases = false; - + private boolean plotByStrand = false; private boolean plotHeatMap = false; + private List<HeatMapLn> heatPlots; + private List<String> selected = new Vector<String>(); + private boolean showGrid = false; protected CoveragePanel(final BamView bamView) { @@ -180,7 +187,7 @@ import net.sf.samtools.SAMRecord; } } - draw(g2, getWidth(), getHeight()); + draw(g2, getWidth(), getHeight(), null); } protected void addRecord(SAMRecord thisRead, int offset, String fileName) @@ -196,7 +203,7 @@ import net.sf.samtools.SAMRecord; } final int col; - if(plotByStrand && thisRead.getReadNegativeStrandFlag()) + if(plotByStrand && !isPlotHeatMap() && thisRead.getReadNegativeStrandFlag()) col = 1; else col = 0; @@ -226,7 +233,7 @@ import net.sf.samtools.SAMRecord; } } - protected void draw(Graphics2D g2, int wid, int hgt) + protected void draw(Graphics2D g2, int wid, int hgt, List<Short> hideBamList) { int size = bamView.bamList.size(); if(includeCombined) @@ -237,35 +244,57 @@ import net.sf.samtools.SAMRecord; else lines = getLineAttributes(size); - + if(plotHeatMap) + heatPlots = new Vector<HeatMapLn>(); Enumeration<String> plotEum = plots.keys(); while(plotEum.hasMoreElements()) { - String fileName = (String) plotEum.nextElement(); - int[][] thisPlot = plots.get(fileName); + String fName = plotEum.nextElement(); + int[][] thisPlot = plots.get(fName); - int index; - if(fileName.equals("-1")) - index = lines.length-1; + int idx; + if(fName.equals("-1")) + idx = lines.length-1; else - index = bamView.bamList.indexOf(fileName); + idx = bamView.bamList.indexOf(fName); + final LineAttributes line = lines[idx]; if(plotHeatMap) - drawHeatMap(g2, hgt, index, thisPlot); + { + if(hideBamList != null) + idx = adjustIdx(idx, hideBamList); + drawHeatMap(g2, hgt, line, idx, thisPlot, fName, + (idx == plots.size()-1)); + } else - drawLinePlot(g2, wid, hgt, index, thisPlot); + drawLinePlot(g2, wid, hgt, line, thisPlot); } } - private void drawLinePlot(final Graphics2D g2, int wid, int hgt, int index, int[][] thisPlot) + /** + * Adjust for BAM's that have been hidden + * @param index + * @param hideBamList + * @return + */ + private int adjustIdx(int index, List<Short> hideBamList) { - g2.setColor(lines[index].getLineColour()); + int shiftIdx = index; + for(short i=0; i<index; i++) + if(hideBamList.contains(i)) + shiftIdx--; + return shiftIdx; + } + + private void drawLinePlot(final Graphics2D g2, int wid, int hgt, LineAttributes line, int[][] thisPlot) + { + g2.setColor(line.getLineColour()); int hgt2 = hgt/2; float maxVal = getValue(max); - if(lines[index].getPlotType() == LineAttributes.PLOT_TYPES[0]) + if(line.getPlotType() == LineAttributes.PLOT_TYPES[0]) { - g2.setStroke(lines[index].getStroke()); + g2.setStroke(line.getStroke()); for(int i=1; i<thisPlot.length; i++) { int x0 = (int) ((((i-1)*(windowSize)) + windowSize/2.f)*pixPerBase); @@ -351,35 +380,52 @@ import net.sf.samtools.SAMRecord; * Draw as heat map * @param g2 * @param hgt - * @param index + * @param line + * @param idx * @param thisPlot */ - private void drawHeatMap(final Graphics2D g2, int hgt, int index, int[][] thisPlot) + private void drawHeatMap(final Graphics2D g2, int hgt, LineAttributes line, int idx, int[][] thisPlot, String fName, boolean lastPlot) { // heat map - int NUMBER_OF_SHADES = 254; - int plotHgt = hgt/plots.size(); - int plotPos = plotHgt * index; - Color definedColours[] = Plot.makeColours(lines[index].getLineColour(), - NUMBER_OF_SHADES); + final int NSHADES = 240; + final int plotHgt = hgt/plots.size(); + final int plotPos = (hgt*idx)/plots.size(); + final Color definedColours[] = Plot.makeColours(line.getLineColour(), NSHADES); + + heatPlots.add(new HeatMapLn(plotPos, plotPos+plotHgt, fName)); float maxVal = getValue(max); for(int i=0; i<thisPlot.length; i++) { int xpos = (int) (i*windowSize*pixPerBase); - // this is a number between 0.0 and 1.0 final float scaledValue = getValue(thisPlot[i][0]) / maxVal; // set color based on value - int colourIndex = + int colourIdx = (int)(definedColours.length * 0.999 * scaledValue); - if(colourIndex > definedColours.length - 1) - colourIndex = definedColours.length - 1; - else if (colourIndex < 0) - colourIndex = 0; - - g2.setColor(definedColours[ colourIndex ]); - g2.fillRect(xpos, plotPos, windowSize, plotHgt); + if(colourIdx > definedColours.length - 1) + colourIdx = definedColours.length - 1; + else if (colourIdx <= 0) + continue; + + g2.setColor(definedColours[ colourIdx ]); + g2.fillRect(xpos, plotPos, (int) (windowSize*2*pixPerBase), plotHgt); + } + + if(showGrid && !lastPlot) + { + g2.setColor(Color.darkGray); + g2.drawLine(0, plotPos+plotHgt-1, bamView.getWidth(), plotPos+plotHgt-1); + } + + if(selected.contains(fName)) + { + g2.setColor(Color.darkGray); + Stroke stroke = g2.getStroke(); + g2.setStroke(new BasicStroke(2.f)); + g2.drawLine(0, plotPos+1, bamView.getWidth(), plotPos+1); + g2.drawLine(0, plotPos+plotHgt-1, bamView.getWidth(), plotPos+plotHgt-1); + g2.setStroke(stroke); } } @@ -433,6 +479,39 @@ import net.sf.samtools.SAMRecord; this.plotHeatMap = plotHeatMap; } + /** + * @return the plotHeatMap + */ + protected boolean isPlotHeatMap() + { + return plotHeatMap; + } + + /** + * Return tooltip text for a given position + * @param e + * @return + */ + public String getToolTipText(int ypos) + { + if(heatPlots == null) + return null; + for(HeatMapLn h: heatPlots) + { + if(ypos > h.yTop && ypos < h.yBtm) + return h.toString(); + } + + return null; + } + + protected void showLabels(boolean showLabel) + { + // + this.showGrid = showLabel; + bamView.repaint(); + } + private void defineOpts() { final JPanel opts = new JPanel(new GridBagLayout()); @@ -509,5 +588,63 @@ import net.sf.samtools.SAMRecord; return; } } + + /** + * Click on heatmap + * @param y + */ + protected void singleClick(boolean isShiftDown, int ypos) + { + if(!isPlotHeatMap()) + return; + + String sel = null; + for(HeatMapLn h: heatPlots) + { + if(ypos > h.yTop && ypos < h.yBtm) + sel = h.fName; + } + + if(selected.contains(sel)) + { + if(!isShiftDown) + selected.clear(); + else + selected.remove(sel); + } + else + { + if(!isShiftDown) + selected.clear(); + selected.add(sel); + } + } + + protected boolean hasSelectedBams() + { + return (selected.size() > 0); + } + + protected List<String> getSelected() + { + return selected; + } + class HeatMapLn + { + private int yTop, yBtm; + private String fName; + HeatMapLn(int yTop, int yBtm, String fName) + { + this.yTop = yTop; + this.yBtm = yBtm; + this.fName = fName; + } + + public String toString() + { + final File f = new File(fName); + return f.getName(); + } + } } \ No newline at end of file diff --git a/uk/ac/sanger/artemis/components/alignment/GroupBamFrame.java b/uk/ac/sanger/artemis/components/alignment/GroupBamFrame.java index 148a80d90a4ed68ec98c554fea6bd47aa9e29caa..ee720fca2e79606505f59dc3b090db93c605fc19 100644 --- a/uk/ac/sanger/artemis/components/alignment/GroupBamFrame.java +++ b/uk/ac/sanger/artemis/components/alignment/GroupBamFrame.java @@ -66,7 +66,7 @@ class GroupBamFrame extends JFrame newGroup.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { - addGroup(bamView, bamFilesMenu, newGroup, jsp); + addGroup(newGroup.getText().trim()); } }); newGroup.addFocusListener(new java.awt.event.FocusAdapter() { @@ -92,7 +92,7 @@ class GroupBamFrame extends JFrame JOptionPane.INFORMATION_MESSAGE); return; } - addGroup(bamView, bamFilesMenu, newGroup, jsp); + addGroup(newGroup.getText().trim()); } }); xBox.add(Box.createHorizontalGlue()); @@ -148,18 +148,16 @@ class GroupBamFrame extends JFrame setVisible(true); } - private void addGroup(final BamView bamView, final JMenu bamFilesMenu, - final JTextField newGroup, final JScrollPane jsp) + protected void addGroup(final String newGroup) { final String tmpGroups[] = new String[groups.length+1]; - for(int i=0; i<groups.length; i++) - tmpGroups[i] = groups[i]; - tmpGroups[tmpGroups.length-1] = newGroup.getText().trim(); + System.arraycopy(groups, 0, tmpGroups, 0, groups.length); + tmpGroups[tmpGroups.length-1] = newGroup; groups = tmpGroups; updateGroupPanel(); updateBamPanel(); - jsp.validate(); + validate(); } private void updateBamPanel() @@ -203,6 +201,29 @@ class GroupBamFrame extends JFrame } } + /** + * Add a BAM to a specified group + * @param bam + * @param group + */ + protected void addToGroup(String bam, String group) + { + final Component cs[] = bamFilesMenu.getMenuComponents(); + for(Component cp : cs) + { + if(cp instanceof JCheckBoxMenuItem) + { + final JCheckBoxMenuItem cbBam = (JCheckBoxMenuItem) cp; + final String thisBam = cbBam.getText(); + if(bam.equals(thisBam)) + { + bamGroupMap.get(cbBam).setSelectedItem(group); + return; + } + } + } + } + /** * For a give BAM file return the group it belongs to. * @param bamName diff --git a/uk/ac/sanger/artemis/components/alignment/LineAttributes.java b/uk/ac/sanger/artemis/components/alignment/LineAttributes.java index cb4cb0355d91c89ee485fb1df4e7844447989bd4..5030f576d8a1ea43a8769138a59d91d3e305602a 100644 --- a/uk/ac/sanger/artemis/components/alignment/LineAttributes.java +++ b/uk/ac/sanger/artemis/components/alignment/LineAttributes.java @@ -155,9 +155,9 @@ public class LineAttributes new Color(0,200,0), Color.blue, Color.magenta, - new Color(204, 204, 51), - new Color(50, 255, 255), - new Color(204, 51, 128), + new Color(100, 100, 0), + new Color(0, 100, 100), + new Color(50, 0, 50), new Color(255, 102, 0), Color.black }; final LineAttributes lines[] = new LineAttributes[numPlots]; diff --git a/uk/ac/sanger/artemis/io/RawStreamSequence.java b/uk/ac/sanger/artemis/io/RawStreamSequence.java index 9337946b0887e3155348548d869b0d403a89de6b..1b6079c9cbc88ce01e8d743ecf4da83ed2e5fba6 100644 --- a/uk/ac/sanger/artemis/io/RawStreamSequence.java +++ b/uk/ac/sanger/artemis/io/RawStreamSequence.java @@ -25,6 +25,7 @@ package uk.ac.sanger.artemis.io; +import uk.ac.sanger.artemis.sequence.BasePattern; import uk.ac.sanger.artemis.util.LinePushBackReader; import java.io.IOException; @@ -45,14 +46,14 @@ public class RawStreamSequence extends StreamSequence * The _character_ positions of the header lines relative to the sequence. * This is in the same order as fasta_header_strings. **/ - private Vector fasta_header_positions = null; + private Vector<Integer> fasta_header_positions = null; /** * This Vector holds the fasta headers (if any) that where seen while * reading the sequence. This is in the same order as * fasta_header_positions. **/ - private Vector fasta_header_strings = null; + private Vector<String> fasta_header_strings = null; /** * Create a new RawStreamSequence object from a stream containing raw @@ -124,14 +125,14 @@ public class RawStreamSequence extends StreamSequence * as it goes. No checks are made on the format of the sequence, apart * from checking that the stream contains only letters. **/ - protected void readSequence(final LinePushBackReader in_stream) + private void readSequence(final LinePushBackReader in_stream) throws IOException { // initialise these here because this method is called before class // variables are initialised - fasta_header_positions = new Vector(); - fasta_header_strings = new Vector(); + fasta_header_positions = new Vector<Integer>(); + fasta_header_strings = new Vector<String>(); final int buffer_capacity = 5000; @@ -141,29 +142,60 @@ public class RawStreamSequence extends StreamSequence String line; int nbase = 0; + boolean validSequenceCheck = true; + String hdrLine = null; while((line = in_stream.readLine()) !=null) { if(line.startsWith(">")) { - final String header_string = line.substring(1); - final Integer header_position = new Integer(nbase); - - fasta_header_strings.addElement(header_string); - fasta_header_positions.addElement(header_position); - - // ignore header lines + storeHeader(line, nbase); + validSequenceCheck = true; + hdrLine = line; continue; } line = line.trim().toLowerCase(); + + if(validSequenceCheck) + { + if(BasePattern.patternType(line) == BasePattern.ILLEGAL_PATTERN) + { + // ignore non-nucleotide sequence + if(hdrLine != null) + { + System.err.println( + "Warning:: ignore non-nucleotide sequence found in "+hdrLine.substring(1)); + fasta_header_strings.remove(hdrLine.substring(1)); + fasta_header_positions.remove(new Integer(nbase)); + } + + while((line = in_stream.readLine()) !=null) + { + if(line.startsWith(">")) + { + storeHeader(line, nbase); + hdrLine = line; + break; + } + } + continue; + } + validSequenceCheck = false; + } if(line.length() > 0) appendChar(line.toCharArray()); nbase += line.length(); + hdrLine = null; } setCounts(); - + } + + private void storeHeader(String line, int nbase) + { + fasta_header_strings.addElement(line.substring(1)); + fasta_header_positions.addElement(new Integer(nbase)); } /** @@ -265,7 +297,7 @@ public class RawStreamSequence extends StreamSequence if(fasta_header_positions != null && fasta_header_positions.size() > 0) { - Vector seen = new Vector(fasta_header_positions.size()); + Vector<Integer> seen = new Vector<Integer>(fasta_header_positions.size()); for(int i = 0 ; i < fasta_header_positions.size(); ++i) { int current_position = ((Integer)fasta_header_positions.elementAt(i)).intValue(); diff --git a/uk/ac/sanger/artemis/sequence/BasePattern.java b/uk/ac/sanger/artemis/sequence/BasePattern.java index df8006fe08a6ac4cb3c6c3e6226c7ff705f5e61f..86ea74fd283d68436245b915c79bd0ab0ca1c1a0 100644 --- a/uk/ac/sanger/artemis/sequence/BasePattern.java +++ b/uk/ac/sanger/artemis/sequence/BasePattern.java @@ -56,7 +56,7 @@ public class BasePattern { /** * A return value of patternType () - illegal characters in the pattern. **/ - private static int ILLEGAL_PATTERN = -1; + public static int ILLEGAL_PATTERN = -1; /** * A return value of patternType () - the pattern contains only the @@ -415,7 +415,7 @@ public class BasePattern { * if the pattern contains only the characters a,t,g,c,r,y,k,m,s,w,n,b,d,h * and v, ie IUC base codes. **/ - private static int patternType (String pattern_string) { + public static int patternType (String pattern_string) { boolean seen_iuc = false; for (int i = 0 ; i < pattern_string.length () ; ++i) {