Skip to content

Commit

Permalink
Base modification display redesign
Browse files Browse the repository at this point in the history
* Coverage track reflects counts of reads with modification above a threshold
* Options to use 2-color or monocolor schemes for all modifications
  • Loading branch information
jrobinso committed Jul 30, 2023
1 parent 8e00678 commit 76ca0be
Show file tree
Hide file tree
Showing 21 changed files with 617 additions and 482 deletions.
4 changes: 4 additions & 0 deletions src/main/java/org/broad/igv/sam/AlignmentCounts.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ public interface AlignmentCounts extends Feature {

int getTotalCount(int pos);

public int getTotalPositiveCount(int pos);

public int getTotalNegativeCount(int pos);

int getTotalQuality(int pos);

int getCount(int pos, byte b);
Expand Down
29 changes: 25 additions & 4 deletions src/main/java/org/broad/igv/sam/AlignmentDataManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.BaseModificationKey;
import org.broad.igv.sam.mods.BaseModificationSet;
import org.broad.igv.sam.reader.AlignmentReader;
import org.broad.igv.sam.reader.AlignmentReaderFactory;
Expand Down Expand Up @@ -69,7 +70,9 @@ public class AlignmentDataManager implements IGVEventObserver {
private Map<String, PEStats> peStats;
private SpliceJunctionHelper.LoadOptions loadOptions;

private Set<String> allBaseModifications = new HashSet<>();
private Set<BaseModificationKey> allBaseModificationKeys = new HashSet<>();

private Set<String> simplexBaseModfications = new HashSet<>();

private Range currentlyLoading;

Expand Down Expand Up @@ -198,8 +201,8 @@ public Map<String, PEStats> getPEStats() {
/**
* Return all base modfications seen in loaded alignments
*/
public Set<String> getAllBaseModifications() {
return allBaseModifications;
public Set<BaseModificationKey> getAllBaseModificationKeys() {
return allBaseModificationKeys;
}

public boolean isPairedEnd() {
Expand Down Expand Up @@ -407,14 +410,28 @@ AlignmentInterval loadInterval(String chr, int start, int end, AlignmentTrack.Re
}

private void updateBaseModfications(List<Alignment> alignments) {

for(Alignment a : alignments) {
List<BaseModificationSet> bmSets = a.getBaseModificationSets();
if(bmSets != null) {
for(BaseModificationSet bms : bmSets) {
allBaseModifications.add(bms.getModification());
allBaseModificationKeys.add(BaseModificationKey.getKey(bms.getBase(), bms.getStrand(), bms.getModification()));
}
}
}
// Search for simplex modifications (single strand read, e.g. C+m with no G-m)
Map<String, BaseModificationKey> tmp = new HashMap<>();
for(BaseModificationKey key : allBaseModificationKeys) {
if(tmp.containsKey(key.getModification())) {
tmp.remove(key);
} else {
tmp.put(key.getModification(), key);
}
}
for(Map.Entry<String, BaseModificationKey> entries : tmp.entrySet()) {
simplexBaseModfications.add(entries.getKey());
simplexBaseModfications.add("NONE_" + entries.getValue().getCanonicalBase());
}
}

public AlignmentTrack.ExperimentType inferType() {
Expand Down Expand Up @@ -573,6 +590,10 @@ public Collection<AlignmentInterval> getLoadedIntervals() {
return intervalCache;
}

public Set<String> getSimplexBaseModifications() {
return simplexBaseModfications;
}

public static class DownsampleOptions {
private boolean downsample;
private int sampleWindowSize;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/broad/igv/sam/AlignmentRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,7 @@ private Color getAlignmentColor(Alignment alignment, AlignmentTrack track) {

case BISULFITE:
case BASE_MODIFICATION:
case BASE_MODIFICATION_C:
case BASE_MODIFICATION_2COLOR:
case SMRT_SUBREAD_IPD:
case SMRT_SUBREAD_PW:
case SMRT_CCS_FWD_IPD:
Expand Down
27 changes: 15 additions & 12 deletions src/main/java/org/broad/igv/sam/AlignmentTrack.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.broad.igv.prefs.IGVPreferences;
import org.broad.igv.prefs.PreferencesManager;
import org.broad.igv.renderer.GraphicUtils;
import org.broad.igv.sam.mods.BaseModficationFilter;
import org.broad.igv.session.Persistable;
import org.broad.igv.track.*;
import org.broad.igv.ui.FontManager;
Expand Down Expand Up @@ -101,8 +102,7 @@ public enum ColorOption {
LINK_STRAND,
YC_TAG,
BASE_MODIFICATION,
BASE_MODIFICATION_5MC,
BASE_MODIFICATION_C,
BASE_MODIFICATION_2COLOR,
SMRT_SUBREAD_IPD,
SMRT_SUBREAD_PW,
SMRT_CCS_FWD_IPD,
Expand All @@ -111,7 +111,7 @@ public enum ColorOption {
SMRT_CCS_REV_PW;

public boolean isBaseMod() {
return this == BASE_MODIFICATION || this == BASE_MODIFICATION_C;
return this == BASE_MODIFICATION || this == BASE_MODIFICATION_2COLOR;
}

public boolean isSMRTKinetics() {
Expand Down Expand Up @@ -1330,7 +1330,7 @@ public static class RenderOptions implements Cloneable, Persistable {
private Boolean hideSmallIndels;
private Integer smallIndelThreshold;

private String basemodFilter;
private BaseModficationFilter basemodFilter;

BisulfiteContext bisulfiteContext = BisulfiteContext.CG;
Map<String, PEStats> peStats;
Expand Down Expand Up @@ -1602,11 +1602,11 @@ public int getSmallIndelThreshold() {
return smallIndelThreshold == null ? getPreferences().getAsInt(SAM_SMALL_INDEL_BP_THRESHOLD) : smallIndelThreshold;
}

public String getBasemodFilter() {
public BaseModficationFilter getBasemodFilter() {
return basemodFilter;
}

public void setBasemodFilter(String basemodFilter) {
public void setBasemodFilter(BaseModficationFilter basemodFilter) {
this.basemodFilter = basemodFilter;
}

Expand Down Expand Up @@ -1707,7 +1707,7 @@ public void marshalXML(Document document, Element element) {
element.setAttribute("showInsertionMarkers", showInsertionMarkers.toString());
}
if (basemodFilter != null) {
element.setAttribute("basemodfilter", basemodFilter);
element.setAttribute("basemodfilter", basemodFilter.toString());
}
}

Expand Down Expand Up @@ -1741,11 +1741,14 @@ public void unmarshalXML(Element element, Integer version) {
final String attributeValue = element.getAttribute("colorOption");
if("BASE_MODIFICATION_6MA".equals(attributeValue)) {
colorOption = ColorOption.BASE_MODIFICATION;
basemodFilter = "a";
basemodFilter = new BaseModficationFilter("a");
} else if("BASE_MODIFICATION_5MC".equals(attributeValue)) {
colorOption = ColorOption.BASE_MODIFICATION_C;
basemodFilter = "m";
} else {
colorOption = ColorOption.BASE_MODIFICATION_2COLOR;
basemodFilter = new BaseModficationFilter(null, 'C');
} else if("BASE_MODIFICATION_C".equals(attributeValue)) {
colorOption = ColorOption.BASE_MODIFICATION;
basemodFilter = new BaseModficationFilter(null, 'C');
}else {
colorOption = ColorOption.valueOf(attributeValue);
}
}
Expand Down Expand Up @@ -1822,7 +1825,7 @@ public void unmarshalXML(Element element, Integer version) {
showInsertionMarkers = Boolean.parseBoolean(element.getAttribute("showInsertionMarkers"));
}
if (element.hasAttribute("basemodfilter")) {
basemodFilter = element.getAttribute("basemodfilter");
basemodFilter = BaseModficationFilter.fromString(element.getAttribute("basemodfilter"));
}

}
Expand Down
45 changes: 22 additions & 23 deletions src/main/java/org/broad/igv/sam/AlignmentTrackMenu.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.broad.igv.sam;

import com.google.gson.annotations.Since;
import htsjdk.samtools.SAMTag;
import htsjdk.samtools.util.Locatable;
import org.broad.igv.Globals;
Expand All @@ -12,6 +11,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.BaseModficationFilter;
import org.broad.igv.sam.mods.BaseModificationUtils;
import org.broad.igv.sashimi.SashimiPlot;
import org.broad.igv.tools.PFMExporter;
Expand Down Expand Up @@ -40,8 +40,6 @@
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -573,9 +571,7 @@ private JRadioButtonMenuItem getColorMenuItem(String label, final AlignmentTrack
mi.setSelected(renderOptions.getColorOption() == option);
mi.addActionListener(aEvt -> {
alignmentTrack.setColorOption(option);
if (option == AlignmentTrack.ColorOption.BASE_MODIFICATION) {
renderOptions.setBasemodFilter(extra);
}
renderOptions.setBasemodFilter(extra == null ? null : new BaseModficationFilter(extra));
alignmentTrack.repaint();
});

Expand Down Expand Up @@ -643,42 +639,45 @@ void addColorByMenuItem() {
// Base modifications
JRadioButtonMenuItem bmMenuItem;

colorMenu.addSeparator();

Set<String> allModifications = dataManager.getAllBaseModifications();
Set<String> allModifications = dataManager.getAllBaseModificationKeys().stream().map(bmKey -> bmKey.getModification()).collect(Collectors.toSet());
if (allModifications.size() > 0) {
BaseModficationFilter filter = renderOptions.getBasemodFilter();

Set<String> cModifications = allModifications.stream().filter(m -> BaseModificationUtils.cModifications.contains(m)).collect(Collectors.toSet());
colorMenu.addSeparator();
if (allModifications.size() > 1) {
bmMenuItem = getColorMenuItem("base modification 2-color (all)", AlignmentTrack.ColorOption.BASE_MODIFICATION_2COLOR);
bmMenuItem.setSelected(renderOptions.getColorOption() == AlignmentTrack.ColorOption.BASE_MODIFICATION_2COLOR && filter == null);
colorMenu.add(bmMenuItem);
group.add(bmMenuItem);
}

for (String m : cModifications) {
for (String m : allModifications) {
String name = BaseModificationUtils.modificationName(m);
bmMenuItem = getColorMenuItem("base modification (" + name + ")", AlignmentTrack.ColorOption.BASE_MODIFICATION_C, m);
bmMenuItem.setSelected(renderOptions.getColorOption() == AlignmentTrack.ColorOption.BASE_MODIFICATION_C && m.equals(renderOptions.getBasemodFilter()));
bmMenuItem = getColorMenuItem("base modification 2-color (" + name + ")", AlignmentTrack.ColorOption.BASE_MODIFICATION_2COLOR, m);
bmMenuItem.setSelected(renderOptions.getColorOption() ==
AlignmentTrack.ColorOption.BASE_MODIFICATION_2COLOR &&
(filter != null && filter.pass(m)));
colorMenu.add(bmMenuItem);
group.add(bmMenuItem);
}

if (cModifications.size() > 1) {
bmMenuItem = getColorMenuItem("CpG modification (all C)", AlignmentTrack.ColorOption.BASE_MODIFICATION_C);
bmMenuItem.setSelected(renderOptions.getColorOption() == AlignmentTrack.ColorOption.BASE_MODIFICATION_C);
colorMenu.addSeparator();
if (allModifications.size() > 1) {
bmMenuItem = getColorMenuItem("base modification (all)", AlignmentTrack.ColorOption.BASE_MODIFICATION);
bmMenuItem.setSelected(renderOptions.getColorOption() == AlignmentTrack.ColorOption.BASE_MODIFICATION && filter == null);
colorMenu.add(bmMenuItem);
group.add(bmMenuItem);
}

colorMenu.addSeparator();
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);
}
if (allModifications.size() > 1) {
bmMenuItem = getColorMenuItem("base modification (all)", AlignmentTrack.ColorOption.BASE_MODIFICATION);
bmMenuItem.setSelected(renderOptions.getColorOption() == AlignmentTrack.ColorOption.BASE_MODIFICATION && renderOptions.getBasemodFilter() == null);
bmMenuItem.setSelected(renderOptions.getColorOption() == AlignmentTrack.ColorOption.BASE_MODIFICATION && (filter != null && filter.pass(m)));
colorMenu.add(bmMenuItem);
group.add(bmMenuItem);
}

}


Expand Down
6 changes: 4 additions & 2 deletions src/main/java/org/broad/igv/sam/CoverageTrack.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.broad.igv.prefs.IGVPreferences;
import org.broad.igv.prefs.PreferencesManager;
import org.broad.igv.renderer.*;
import org.broad.igv.sam.mods.BaseModficationFilter;
import org.broad.igv.sam.mods.BaseModificationCoverageRenderer;
import org.broad.igv.tdf.TDFDataSource;
import org.broad.igv.tdf.TDFReader;
Expand Down Expand Up @@ -505,6 +506,7 @@ private void paint(final RenderContext context, final Rectangle rect, final Alig
}

// Second pass -- potentially overlay mismatches
Set<String> simplexModifications = dataManager.getSimplexBaseModifications();
for (int idx = 0; idx < nPoints; idx++) {

int pos = isSparse ? ((SparseAlignmentCounts) alignmentCounts).getPosition(idx) : start + idx * step;
Expand All @@ -531,8 +533,8 @@ private void paint(final RenderContext context, final Rectangle rect, final Alig
drawBarBisulfite(context, pX, bottomY, dX, barHeight, totalCount, bc);
}
} else if (colorOption.isBaseMod()) {
String basemodFilter = alignmentTrack != null ? alignmentTrack.getRenderOptions().getBasemodFilter() : null;
BaseModificationCoverageRenderer.drawModifications(context, pX, bottomY, dX, barHeight, pos, alignmentCounts, colorOption, basemodFilter);
BaseModficationFilter basemodFilter = alignmentTrack != null ? alignmentTrack.getRenderOptions().getBasemodFilter() : null;
BaseModificationCoverageRenderer.drawModifications(context, pX, bottomY, dX, barHeight, pos, alignmentCounts, colorOption, basemodFilter, simplexModifications);
} else {
if (refBases != null) {
int refIdx = pos - intervalStart;
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/org/broad/igv/sam/DenseAlignmentCounts.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,30 @@ public int getTotalCount(int pos) {
return 0;
} else {
return posTotal[offset] + negTotal[offset];
}
}

public int getTotalPositiveCount(int pos) {
int offset = pos - start;
if (offset < 0 || offset >= posA.length) {
if (log.isDebugEnabled()) {
log.debug("Position out of range: " + pos + " (valid range - " + start + "-" + end);
}
return 0;
} else {
return posTotal[offset];
}
}

public int getTotalNegativeCount(int pos) {
int offset = pos - start;
if (offset < 0 || offset >= posA.length) {
if (log.isDebugEnabled()) {
log.debug("Position out of range: " + pos + " (valid range - " + start + "-" + end);
}
return 0;
} else {
return negTotal[offset];
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/main/java/org/broad/igv/sam/ReducedMemoryAlignment.java
Original file line number Diff line number Diff line change
Expand Up @@ -468,10 +468,19 @@ public int getTotalCount(int pos) {
return 0;
} else {
return (int) Math.round(total[offset]);

}
}

@Override
public int getTotalPositiveCount(int pos) {
throw new RuntimeException(" Method getTotalPositiveCount not implemented");
}

@Override
public int getTotalNegativeCount(int pos) {
throw new RuntimeException(" Method getTotalNegativeCount not implemented");
}

@Override
public void incCounts(Alignment alignment) {

Expand Down
26 changes: 26 additions & 0 deletions src/main/java/org/broad/igv/sam/SparseAlignmentCounts.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,32 @@ public int getTotalCount(int pos) {
}
}

@Override
public int getTotalPositiveCount(int pos) {
if (!indexMap.containsKey(pos)) {
if (log.isDebugEnabled()) {
log.debug("Position out of range: " + pos + " (valid range - " + start + "-" + end);
}
return 0;
} else {
int idx = getIndex(pos);
return getCountFromList(posTotal, idx);
}
}

@Override
public int getTotalNegativeCount(int pos) {
if (!indexMap.containsKey(pos)) {
if (log.isDebugEnabled()) {
log.debug("Position out of range: " + pos + " (valid range - " + start + "-" + end);
}
return 0;
} else {
int idx = getIndex(pos);
return getCountFromList(negTotal, idx);

}
}

public int getTotalQuality(int pos) {
if (!indexMap.containsKey(pos)) {
Expand Down
Loading

0 comments on commit 76ca0be

Please sign in to comment.