From 044c7113a9e70e6a2f6704d0d0c226f70307dd7a Mon Sep 17 00:00:00 2001 From: jrobinso <933148+jrobinso@users.noreply.github.com> Date: Tue, 27 Jun 2023 12:34:42 -0700 Subject: [PATCH] basemod enhancements -- allow selection of specific modifications --- .../broad/igv/sam/AlignmentDataManager.java | 25 +++++++- .../org/broad/igv/sam/AlignmentRenderer.java | 2 +- .../org/broad/igv/sam/AlignmentTrack.java | 34 ++++++++--- .../org/broad/igv/sam/AlignmentTrackMenu.java | 61 ++++++++++++++----- .../java/org/broad/igv/sam/CoverageTrack.java | 3 +- .../BaseModificationCoverageRenderer.java | 5 +- .../sam/mods/BaseModificationRenderer.java | 5 +- .../igv/sam/mods/BaseModificationUtils.java | 8 ++- 8 files changed, 110 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/broad/igv/sam/AlignmentDataManager.java b/src/main/java/org/broad/igv/sam/AlignmentDataManager.java index d2c351eba2..6412852b53 100644 --- a/src/main/java/org/broad/igv/sam/AlignmentDataManager.java +++ b/src/main/java/org/broad/igv/sam/AlignmentDataManager.java @@ -36,6 +36,7 @@ import org.broad.igv.feature.genome.Genome; import org.broad.igv.prefs.IGVPreferences; import org.broad.igv.prefs.PreferencesManager; +import org.broad.igv.sam.mods.BaseModificationSet; import org.broad.igv.sam.reader.AlignmentReader; import org.broad.igv.sam.reader.AlignmentReaderFactory; import org.broad.igv.track.Track; @@ -58,17 +59,18 @@ public class AlignmentDataManager implements IGVEventObserver { private static Logger log = LogManager.getLogger(AlignmentDataManager.class); private final AlignmentReader reader; - private AlignmentTrack alignmentTrack; private CoverageTrack coverageTrack; private Set subscribedTracks; - private List intervalCache; private ResourceLocator locator; private HashMap chrMappings = new HashMap(); private AlignmentTileLoader loader; private Map peStats; private SpliceJunctionHelper.LoadOptions loadOptions; + + private Set allBaseModifications = new HashSet<>(); + private Range currentlyLoading; public AlignmentDataManager(ResourceLocator locator, Genome genome) throws IOException { @@ -193,6 +195,13 @@ public Map getPEStats() { return peStats; } + /** + * Return all base modfications seen in loaded alignments + */ + public Set getAllBaseModifications() { + return allBaseModifications; + } + public boolean isPairedEnd() { return getLoader().isPairedEnd(); } @@ -393,9 +402,21 @@ AlignmentInterval loadInterval(String chr, int start, int end, AlignmentTrack.Re downsampleOptions, peStats, bisulfiteContext, renderOptions); List alignments = t.getAlignments(); List downsampledIntervals = t.getDownsampledIntervals(); + this.updateBaseModfications(alignments); return new AlignmentInterval(chr, start, end, alignments, t.getCounts(), spliceJunctionHelper, downsampledIntervals); } + private void updateBaseModfications(List alignments) { + for(Alignment a : alignments) { + List bmSets = a.getBaseModificationSets(); + if(bmSets != null) { + for(BaseModificationSet bms : bmSets) { + allBaseModifications.add(bms.getModification()); + } + } + } + } + public AlignmentTrack.ExperimentType inferType() { ReadStats readStats = new ReadStats(); List sample = AlignmentUtils.firstAlignments(reader, 100); diff --git a/src/main/java/org/broad/igv/sam/AlignmentRenderer.java b/src/main/java/org/broad/igv/sam/AlignmentRenderer.java index 10ccaab96e..b09f76004d 100644 --- a/src/main/java/org/broad/igv/sam/AlignmentRenderer.java +++ b/src/main/java/org/broad/igv/sam/AlignmentRenderer.java @@ -853,7 +853,7 @@ private void drawAlignment( // Base modification if (colorOption.isBaseMod()) { - BaseModificationRenderer.drawModifications(alignment, bpStart, locScale, rowRect, context.getGraphics(), colorOption); + BaseModificationRenderer.drawModifications(alignment, bpStart, locScale, rowRect, context.getGraphics(), colorOption, renderOptions.getBasemodFilter()); } // Kinetic data diff --git a/src/main/java/org/broad/igv/sam/AlignmentTrack.java b/src/main/java/org/broad/igv/sam/AlignmentTrack.java index 93b3d57c7c..0cb4c7222d 100644 --- a/src/main/java/org/broad/igv/sam/AlignmentTrack.java +++ b/src/main/java/org/broad/igv/sam/AlignmentTrack.java @@ -131,7 +131,6 @@ public boolean isSMRTKinetics() { } - public enum ShadeAlignmentsOption { NONE("none"), MAPPING_QUALITY_HIGH("mapping quality high"), @@ -252,9 +251,11 @@ public BisulfiteContext getMatchingBisulfiteContext(final byte[] reference, fina return (matchesContext) ? this : null; } - public String getLabel() { return label; } + public String getLabel() { + return label; + } - BisulfiteContext(String label, byte[] preContext, byte[] postContext){ + BisulfiteContext(String label, byte[] preContext, byte[] postContext) { this.label = label; this.preContext = preContext; this.postContext = postContext; @@ -389,7 +390,7 @@ public void receiveEvent(Object event) { break; } - } else if( event instanceof DataLoadedEvent){ + } else if (event instanceof DataLoadedEvent) { final DataLoadedEvent dataLoaded = (DataLoadedEvent) event; actionToPerformOnFrameLoad.computeIfPresent(dataLoaded.getReferenceFrame(), (k, v) -> { v.accept(k); @@ -747,7 +748,7 @@ public void renderExpandedInsertion(InsertionMarker insertionMarker, RenderConte boolean leaveMargin = getDisplayMode() != DisplayMode.SQUISHED; // Insertion interval - if(this.renderOptions.isShowInsertionMarkers()) { + if (this.renderOptions.isShowInsertionMarkers()) { Graphics2D g = context.getGraphic2DForColor(Color.red); Rectangle iRect = new Rectangle(inputRect.x, insertionRect.y, inputRect.width, insertionRect.height); g.fill(iRect); @@ -830,14 +831,14 @@ private InsertionInterval getInsertionInterval(ReferenceFrame frame, int x, int public void sortRows(final SortOption option, final Double location, final String tag, final boolean invertSort, final Set priorityRecords) { final List frames = FrameManager.getFrames(); for (ReferenceFrame frame : frames) { - Consumer sort = (ReferenceFrame f) -> { + Consumer sort = (ReferenceFrame f) -> { final AlignmentInterval interval = getDataManager().getLoadedInterval(f); final double actloc = location != null ? location : f.getCenter(); interval.sortRows(option, actloc, tag, invertSort, priorityRecords); }; //If the data is loaded sort now, otherwise delay until we get a message that it is loaded. - if(getDataManager().isLoaded(frame)){ - sort.accept(frame); + if (getDataManager().isLoaded(frame)) { + sort.accept(frame); } else { log.debug("Attempt to sort alignments prior to loading"); actionToPerformOnFrameLoad.put(frame, sort); @@ -936,7 +937,6 @@ public String getValueStringAt(String chr, double position, int mouseX, int mous } - Alignment getAlignmentAt(final TrackClickEvent te) { MouseEvent e = te.getMouseEvent(); final ReferenceFrame frame = te.getFrame(); @@ -1331,6 +1331,8 @@ public static class RenderOptions implements Cloneable, Persistable { private Boolean hideSmallIndels; private Integer smallIndelThreshold; + private String basemodFilter; + BisulfiteContext bisulfiteContext = BisulfiteContext.CG; Map peStats; @@ -1601,6 +1603,13 @@ public int getSmallIndelThreshold() { return smallIndelThreshold == null ? getPreferences().getAsInt(SAM_SMALL_INDEL_BP_THRESHOLD) : smallIndelThreshold; } + public String getBasemodFilter() { + return basemodFilter; + } + + public void setBasemodFilter(String basemodFilter) { + this.basemodFilter = basemodFilter; + } @Override public void marshalXML(Document document, Element element) { @@ -1698,6 +1707,9 @@ public void marshalXML(Document document, Element element) { if (showInsertionMarkers != null) { element.setAttribute("showInsertionMarkers", showInsertionMarkers.toString()); } + if (basemodFilter != null) { + element.setAttribute("basemodfilter", basemodFilter); + } } @@ -1800,6 +1812,10 @@ public void unmarshalXML(Element element, Integer version) { if (element.hasAttribute("showInsertionMarkers")) { showInsertionMarkers = Boolean.parseBoolean(element.getAttribute("showInsertionMarkers")); } + if (element.hasAttribute("basemodfilter")) { + basemodFilter = element.getAttribute("basemodfilter"); + } + } } diff --git a/src/main/java/org/broad/igv/sam/AlignmentTrackMenu.java b/src/main/java/org/broad/igv/sam/AlignmentTrackMenu.java index 79c0b16609..d84666367e 100644 --- a/src/main/java/org/broad/igv/sam/AlignmentTrackMenu.java +++ b/src/main/java/org/broad/igv/sam/AlignmentTrackMenu.java @@ -12,6 +12,7 @@ import org.broad.igv.logging.LogManager; import org.broad.igv.logging.Logger; import org.broad.igv.prefs.PreferencesManager; +import org.broad.igv.sam.mods.BaseModificationUtils; import org.broad.igv.sashimi.SashimiPlot; import org.broad.igv.tools.PFMExporter; import org.broad.igv.track.SequenceTrack; @@ -142,7 +143,7 @@ class AlignmentTrackMenu extends IGVPopupMenu { // Select alignment items addSeparator(); - addSelectByNameItem(alignmentTrack, e); + addSelectByNameItem(alignmentTrack, e); addClearSelectionsMenuItem(); // Copy items @@ -195,7 +196,7 @@ private void addShowChimericRegions(final AlignmentTrack alignmentTrack, final T add(item); } - private void addShowDiagram(final TrackClickEvent e, final Alignment clickedAlignment){ + private void addShowDiagram(final TrackClickEvent e, final Alignment clickedAlignment) { JMenuItem item = new JMenuItem("Supplementary Reads Diagram"); if (clickedAlignment != null && clickedAlignment.getAttribute(SAMTag.SA.name()) != null) { item.setEnabled(true); @@ -472,8 +473,8 @@ void addGroupMenuItem(final TrackClickEvent te) {//ReferenceFrame frame) { private void groupAlignments(AlignmentTrack.GroupOption option, String tag, Range pos) { - if(alignmentTrack.getPreferences().getAsBoolean(SAM_GROUP_ALL)) { - for(AlignmentTrack t : IGV.getInstance().getAlignmentTracks()) { + if (alignmentTrack.getPreferences().getAsBoolean(SAM_GROUP_ALL)) { + for (AlignmentTrack t : IGV.getInstance().getAlignmentTracks()) { t.groupAlignments(option, tag, pos); } } else { @@ -563,10 +564,18 @@ public void addFilterMenuItem() { } private JRadioButtonMenuItem getColorMenuItem(String label, final AlignmentTrack.ColorOption option) { + return getColorMenuItem(label, option, null); + + } + + private JRadioButtonMenuItem getColorMenuItem(String label, final AlignmentTrack.ColorOption option, String extra) { JRadioButtonMenuItem mi = new JRadioButtonMenuItem(label); mi.setSelected(renderOptions.getColorOption() == option); mi.addActionListener(aEvt -> { alignmentTrack.setColorOption(option); + if (option == AlignmentTrack.ColorOption.BASE_MODIFICATION) { + renderOptions.setBasemodFilter(extra); + } alignmentTrack.repaint(); }); @@ -632,18 +641,42 @@ void addColorByMenuItem() { colorMenu.add(getBisulfiteContextMenuItem(group)); // Base modifications - mappings.clear(); - mappings.put("base modification", AlignmentTrack.ColorOption.BASE_MODIFICATION); - mappings.put("base modification (5mC)", AlignmentTrack.ColorOption.BASE_MODIFICATION_5MC); - mappings.put("base modification (all C)", AlignmentTrack.ColorOption.BASE_MODIFICATION_C); - mappings.put("base modification (6mA)", AlignmentTrack.ColorOption.BASE_MODIFICATION_6MA); + JRadioButtonMenuItem bmMenuItem; + colorMenu.addSeparator(); - for (Map.Entry el : mappings.entrySet()) { - JRadioButtonMenuItem mi = getColorMenuItem(el.getKey(), el.getValue()); - colorMenu.add(mi); - group.add(mi); + + Set allModifications = dataManager.getAllBaseModifications(); + + bmMenuItem = getColorMenuItem("CpG modification (5mC)", AlignmentTrack.ColorOption.BASE_MODIFICATION_5MC); + bmMenuItem.setSelected(renderOptions.getColorOption() == AlignmentTrack.ColorOption.BASE_MODIFICATION_5MC); + bmMenuItem.setEnabled(allModifications.contains("m")); + colorMenu.add(bmMenuItem); + group.add(bmMenuItem); + + bmMenuItem = getColorMenuItem("CpG modification (all C)", AlignmentTrack.ColorOption.BASE_MODIFICATION_C); + bmMenuItem.setSelected(renderOptions.getColorOption() == AlignmentTrack.ColorOption.BASE_MODIFICATION_C); + bmMenuItem.setEnabled(allModifications.contains("m") || allModifications.contains("h")); + colorMenu.add(bmMenuItem); + group.add(bmMenuItem); + + colorMenu.addSeparator(); + + bmMenuItem = getColorMenuItem("base modification (all)", AlignmentTrack.ColorOption.BASE_MODIFICATION); + bmMenuItem.setSelected(renderOptions.getColorOption() == AlignmentTrack.ColorOption.BASE_MODIFICATION && renderOptions.getBasemodFilter() == null); + bmMenuItem.setEnabled(!allModifications.isEmpty()); + colorMenu.add(bmMenuItem); + group.add(bmMenuItem); + + for(String m : allModifications) { + String name = BaseModificationUtils.modificationName(m); + bmMenuItem = getColorMenuItem("base modification (" + name + ")", AlignmentTrack.ColorOption.BASE_MODIFICATION, m); + bmMenuItem.setSelected(renderOptions.getColorOption() == AlignmentTrack.ColorOption.BASE_MODIFICATION && m.equals(renderOptions.getBasemodFilter())); + colorMenu.add(bmMenuItem); + group.add(bmMenuItem); } + + // SMRT kinetics if (alignmentTrack.getPreferences().getAsBoolean(SMRT_KINETICS_SHOW_OPTIONS)) { // Show additional options to help visualize SMRT kinetics data mappings.clear(); @@ -652,7 +685,7 @@ void addColorByMenuItem() { mappings.put("SMRT CCS fwd-strand aligned IPD", AlignmentTrack.ColorOption.SMRT_CCS_FWD_IPD); mappings.put("SMRT CCS fwd-strand aligned PW", AlignmentTrack.ColorOption.SMRT_CCS_FWD_PW); mappings.put("SMRT CCS rev-strand aligned IPD", AlignmentTrack.ColorOption.SMRT_CCS_REV_IPD); - mappings.put("SMRT CCS rev-strand aligned PW",AlignmentTrack.ColorOption.SMRT_CCS_REV_PW); + mappings.put("SMRT CCS rev-strand aligned PW", AlignmentTrack.ColorOption.SMRT_CCS_REV_PW); colorMenu.addSeparator(); for (Map.Entry el : mappings.entrySet()) { JRadioButtonMenuItem mi = getColorMenuItem(el.getKey(), el.getValue()); diff --git a/src/main/java/org/broad/igv/sam/CoverageTrack.java b/src/main/java/org/broad/igv/sam/CoverageTrack.java index 5a0730d24f..d386f6a2e0 100644 --- a/src/main/java/org/broad/igv/sam/CoverageTrack.java +++ b/src/main/java/org/broad/igv/sam/CoverageTrack.java @@ -531,7 +531,8 @@ private void paint(final RenderContext context, final Rectangle rect, final Alig drawBarBisulfite(context, pX, bottomY, dX, barHeight, totalCount, bc); } } else if (colorOption.isBaseMod()) { - BaseModificationCoverageRenderer.drawModifications(context, pX, bottomY, dX, barHeight, pos, alignmentCounts, colorOption); + String basemodFilter = alignmentTrack != null ? alignmentTrack.getRenderOptions().getBasemodFilter() : null; + BaseModificationCoverageRenderer.drawModifications(context, pX, bottomY, dX, barHeight, pos, alignmentCounts, colorOption, basemodFilter); } else { if (refBases != null) { int refIdx = pos - intervalStart; diff --git a/src/main/java/org/broad/igv/sam/mods/BaseModificationCoverageRenderer.java b/src/main/java/org/broad/igv/sam/mods/BaseModificationCoverageRenderer.java index 5c0a73596e..e0e137d0e5 100644 --- a/src/main/java/org/broad/igv/sam/mods/BaseModificationCoverageRenderer.java +++ b/src/main/java/org/broad/igv/sam/mods/BaseModificationCoverageRenderer.java @@ -18,7 +18,8 @@ public static void drawModifications(RenderContext context, int barHeight, int pos, AlignmentCounts alignmentCounts, - ColorOption colorOption) { + ColorOption colorOption, + String basemodFilter) { switch (colorOption) { case BASE_MODIFICATION_5MC: @@ -31,7 +32,7 @@ public static void drawModifications(RenderContext context, draw(context, pX, pBottom, dX, barHeight, pos, alignmentCounts, "a"); break; default: - draw(context, pX, pBottom, dX, barHeight, pos, alignmentCounts, null); + draw(context, pX, pBottom, dX, barHeight, pos, alignmentCounts, basemodFilter); } } diff --git a/src/main/java/org/broad/igv/sam/mods/BaseModificationRenderer.java b/src/main/java/org/broad/igv/sam/mods/BaseModificationRenderer.java index d0d2651571..8f07af07c7 100644 --- a/src/main/java/org/broad/igv/sam/mods/BaseModificationRenderer.java +++ b/src/main/java/org/broad/igv/sam/mods/BaseModificationRenderer.java @@ -15,7 +15,8 @@ public static void drawModifications( double locScale, Rectangle rowRect, Graphics g, - AlignmentTrack.ColorOption colorOption) { + AlignmentTrack.ColorOption colorOption, + String basemodFilter) { switch (colorOption) { case BASE_MODIFICATION_5MC: @@ -28,7 +29,7 @@ public static void drawModifications( draw(alignment, bpStart, locScale, rowRect, g, "a"); break; default: - draw(alignment, bpStart, locScale, rowRect, g, null); + draw(alignment, bpStart, locScale, rowRect, g, basemodFilter); } } diff --git a/src/main/java/org/broad/igv/sam/mods/BaseModificationUtils.java b/src/main/java/org/broad/igv/sam/mods/BaseModificationUtils.java index 1f24423253..5912a4190d 100644 --- a/src/main/java/org/broad/igv/sam/mods/BaseModificationUtils.java +++ b/src/main/java/org/broad/igv/sam/mods/BaseModificationUtils.java @@ -40,8 +40,12 @@ public class BaseModificationUtils { public static String valueString(String modification, byte likelihood) { int l = (int) (100.0 * Byte.toUnsignedInt(likelihood) / 255); - return "Base modification: " + - ((codeValues.containsKey(modification)) ? codeValues.get(modification) : "Uknown") + " (" + l + "%)"; + return "Base modification: " + modificationName(modification) + " (" + l + "%)"; + } + + + public static String modificationName(String modification) { + return ((codeValues.containsKey(modification)) ? codeValues.get(modification) : modification); }