Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding additional options for duplicate reads. #1542

Merged
merged 3 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 57 additions & 60 deletions src/main/java/org/broad/igv/sam/AlignmentPacker.java
Original file line number Diff line number Diff line change
Expand Up @@ -438,69 +438,64 @@ private Object getGroupValue(Alignment al, AlignmentTrack.RenderOptions renderOp
Range pos = renderOptions.getGroupByPos();
String readNameParts[], movieName, zmw;

switch (groupBy) {
case CLUSTER:
return al.getClusterName();
case STRAND:
return al.isNegativeStrand() ? "-" : "+";
case SAMPLE:
return al.getSample();
case LIBRARY:
return al.getLibrary();
case READ_GROUP:
return al.getReadGroup();
case LINKED:
return (al instanceof LinkedAlignment) ? "Linked" : "";
case PHASE:
return al.getAttribute("HP");
case TAG:
return switch (groupBy) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a big improvement.

case CLUSTER -> al.getClusterName();
case STRAND -> al.isNegativeStrand() ? "-" : "+";
case SAMPLE -> al.getSample();
case LIBRARY -> al.getLibrary();
case READ_GROUP -> al.getReadGroup();
case LINKED -> (al instanceof LinkedAlignment) ? "Linked" : "";
case PHASE -> al.getAttribute("HP");
case TAG -> {
Object tagValue = tag == null ? null : al.getAttribute(tag);
if (tagValue == null) {
return null;
yield null;
} else if (tagValue instanceof Integer || tagValue instanceof Float || tagValue instanceof Double) {
return tagValue;
yield tagValue;
} else {
return tagValue.toString();
yield tagValue.toString();
}
case FIRST_OF_PAIR_STRAND:
}
case FIRST_OF_PAIR_STRAND -> {
Strand strand = al.getFirstOfPairStrand();
String strandString = strand == Strand.NONE ? null : strand.toString();
return strandString;
case READ_ORDER:
yield strand == Strand.NONE ? null : strand.toString();
}
case READ_ORDER -> {
if (al.isPaired() && al.isFirstOfPair()) {
return "FIRST";
yield "FIRST";
} else if (al.isPaired() && al.isSecondOfPair()) {
return "SECOND";
yield "SECOND";
} else {
return "";
yield "";
}
case PAIR_ORIENTATION:
}
case PAIR_ORIENTATION -> {
PEStats peStats = AlignmentRenderer.getPEStats(al, renderOptions);
AlignmentTrack.OrientationType type = AlignmentRenderer.getOrientationType(al, peStats);
if (type == null) {
return AlignmentTrack.OrientationType.UNKNOWN.name();
yield AlignmentTrack.OrientationType.UNKNOWN.name();
}
return type.name();
case MATE_CHROMOSOME:
yield type.name();
}
case MATE_CHROMOSOME -> {
ReadMate mate = al.getMate();
if (mate == null) {
return null;
yield null;
}
if (!mate.isMapped()) {
return "UNMAPPED";
yield "UNMAPPED";
} else {
return mate.getChr();
yield mate.getChr();
}
case CHIMERIC:
return al.getAttribute(SAMTag.SA.name()) != null ? "CHIMERIC" : "";
case SUPPLEMENTARY:
return al.isSupplementary() ? "SUPPLEMENTARY" : "";
case REFERENCE_CONCORDANCE:
return !al.isProperPair() ||
al.getCigarString().toUpperCase().contains("S") ||
al.isSupplementary() ?
"DISCORDANT" : "";
case BASE_AT_POS:
}
case NONE -> null;
case CHIMERIC -> al.getAttribute(SAMTag.SA.name()) != null ? "CHIMERIC" : "";
case SUPPLEMENTARY -> al.isSupplementary() ? "SUPPLEMENTARY" : "";
case REFERENCE_CONCORDANCE -> !al.isProperPair() ||
al.getCigarString().toUpperCase().contains("S") ||
al.isSupplementary() ?
"DISCORDANT" : "";
case BASE_AT_POS -> {
// Use a string prefix to enforce grouping rules:
// 1: alignments with a base at the position
// 2: alignments with a gap at the position
Expand All @@ -513,14 +508,15 @@ private Object getGroupValue(Alignment al, AlignmentTrack.RenderOptions renderOp

byte[] baseAtPos = new byte[]{al.getBase(pos.getStart())};
if (baseAtPos[0] == 0) { // gap at position
return "2:";
yield "2:";
} else { // base at position
return "1:" + new String(baseAtPos);
yield "1:" + new String(baseAtPos);
}
} else { // does not overlap position
return "3:";
yield "3:";
}
case INSERTION_AT_POS:
}
case INSERTION_AT_POS -> {
// Use a string prefix to enforce grouping rules:
// 1: alignments with a base at the position
// 2: alignments with a gap at the position
Expand All @@ -538,31 +534,32 @@ private Object getGroupValue(Alignment al, AlignmentTrack.RenderOptions renderOp
if (rightInsertion != null) {
insertionBaseCount += rightInsertion.getLength();
}
return insertionBaseCount;
yield insertionBaseCount;

} else {
return 0;
yield 0;
}

case MOVIE: // group PacBio reads by movie
}
case MOVIE -> {
readNameParts = al.getReadName().split("/");
if (readNameParts.length < 3) {
return "";
yield "";
}
movieName = readNameParts[0];
return movieName;
case ZMW: // group PacBio reads by ZMW
yield movieName; // group PacBio reads by movie
}
case ZMW -> {
readNameParts = al.getReadName().split("/");
if (readNameParts.length < 3) {
return "";
yield "";
}
movieName = readNameParts[0];
zmw = readNameParts[1];
return movieName + "/" + zmw;
case MAPPING_QUALITY:
return al.getMappingQuality();
}
return null;
yield movieName + "/" + zmw; // group PacBio reads by ZMW
}
case MAPPING_QUALITY -> al.getMappingQuality();
case DUPLICATE -> al.isDuplicate() ? "duplicate" : "non-duplicate";
};
}

interface BucketCollection {
Expand Down
49 changes: 45 additions & 4 deletions src/main/java/org/broad/igv/sam/AlignmentRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import org.broad.igv.prefs.IGVPreferences;
import org.broad.igv.prefs.PreferencesManager;
import org.broad.igv.renderer.GraphicUtils;
import org.broad.igv.renderer.SequenceRenderer;
import org.broad.igv.sam.AlignmentTrack.ColorOption;
import org.broad.igv.sam.BisulfiteBaseInfo.DisplayStatus;
import org.broad.igv.sam.mods.BaseModificationRenderer;
Expand All @@ -55,6 +54,7 @@

import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -118,6 +118,7 @@ public class AlignmentRenderer {
private static Map<String, AlignmentTrack.OrientationType> f2f1OrientationTypes;
private static Map<String, AlignmentTrack.OrientationType> rfOrientationTypes;
private static Map<AlignmentTrack.OrientationType, Color> typeToColorMap;
private static final Map<Color, TexturePaint> duplicateTextures = new HashMap<>();

public static final HSLColorTable tenXColorTable1 = new HSLColorTable(30);
public static final HSLColorTable tenXColorTable2 = new HSLColorTable(270);
Expand Down Expand Up @@ -325,11 +326,11 @@ public void renderAlignments(List<Alignment> alignments,
int lastPixelDrawn = -1;

for (Alignment alignment : alignments) {
// Compute the start and dend of the alignment in pixels
// Compute the start and end of the alignment in pixels
double pixelStart = ((alignment.getStart() - origin) / locScale);
double pixelEnd = ((alignment.getEnd() - origin) / locScale);

// If any any part of the feature fits in the track rectangle draw it
// If any part of the feature fits in the track rectangle draw it
if (pixelEnd < rowRect.x || pixelStart > rowRect.getMaxX()) {
continue;
}
Expand Down Expand Up @@ -757,7 +758,18 @@ private void drawAlignment(
else if (block.getCigarOperator() == 'X') g = context.getGraphics2D("MISMATCH");
}

g.fill(blockShape);
if(renderOptions.getDuplicatesOption() == AlignmentTrack.DuplicatesOption.TEXTURE && alignment.isDuplicate()) {
final Graphics2D tg = (Graphics2D) g.create();

final TexturePaint tp = getDuplicateTexture(tg.getColor());
// Add the texture paint to the graphics context.
tg.setPaint(tp);
tg.fill(blockShape);
tg.dispose();
} else {
g.fill(blockShape);
}

if (outlineGraphics != null) {
outlineGraphics.draw(blockShape);
}
Expand Down Expand Up @@ -944,6 +956,35 @@ private void drawAlignment(

}

/**
* get a texture to apply to duplicate reads, caches the created textures according to their color
* @param baseColor the color to render the read
* @return a texture matching the base color with shading
*/
private TexturePaint getDuplicateTexture(Color baseColor) {
return duplicateTextures.computeIfAbsent(baseColor, color -> {
BufferedImage texture = new BufferedImage(5, 5, BufferedImage.TYPE_INT_RGB);
Graphics2D big = texture.createGraphics();
//Render into the BufferedImage graphics to create the texture
big.setColor(baseColor);
big.fillRect(0, 0, 5, 5);

final Color darker = baseColor.darker();
big.setColor(darker);
// hash pattern, probably better to save it as a bitmap somewhere
big.drawLine(0, 2, 0, 3);
big.drawLine(1, 3, 1, 4);
big.drawLine(2, 4, 2, 4);
big.drawLine(2, 0, 2, 0);
big.drawLine(3, 0, 3, 1);
big.drawLine(4, 1, 4, 2);
big.dispose();
// Create a texture paint from the buffered image
Rectangle r = new Rectangle(0, 0, 5, 5);
return new TexturePaint(texture, r);
});
}

private static void drawClippedEnds(final Graphics2D g, final int[] xPoly, final int[] yPoly,
final boolean drawLeftClip, final boolean drawRightClip,
final SupplementaryAlignment.SupplementaryNeighbors sri) {
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/org/broad/igv/sam/AlignmentTileLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
package org.broad.igv.sam;

import htsjdk.samtools.SAMFileHeader;
import htsjdk.samtools.SAMTag;
import htsjdk.samtools.util.CloseableIterator;
import org.broad.igv.event.IGVEvent;
import org.broad.igv.logging.*;
Expand All @@ -43,7 +42,6 @@
import org.broad.igv.util.ObjectCache;
import org.broad.igv.util.RuntimeUtils;

import javax.swing.*;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.text.DecimalFormat;
Expand Down Expand Up @@ -134,7 +132,10 @@ AlignmentTile loadTile(String chr,
boolean filterSecondaryAlignments = prefMgr.getAsBoolean(SAM_FILTER_SECONDARY_ALIGNMENTS);
boolean filterSupplementaryAlignments = prefMgr.getAsBoolean(SAM_FILTER_SUPPLEMENTARY_ALIGNMENTS);
ReadGroupFilter filter = ReadGroupFilter.getFilter();
boolean filterDuplicates = prefMgr.getAsBoolean(SAM_FILTER_DUPLICATES);
boolean filterDuplicates = renderOptions != null
? renderOptions.getDuplicatesOption() == AlignmentTrack.DuplicatesOption.FILTER
: prefMgr.getAsBoolean(SAM_FILTER_DUPLICATES);

int qualityThreshold = prefMgr.getAsInt(SAM_QUALITY_THRESHOLD);
int alignmentScoreTheshold = prefMgr.getAsInt(SAM_ALIGNMENT_SCORE_THRESHOLD);

Expand Down
40 changes: 39 additions & 1 deletion src/main/java/org/broad/igv/sam/AlignmentTrack.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ public boolean isSMRTKinetics() {
}
}

public enum DuplicatesOption {
FILTER("filter duplicates", true),
SHOW("show duplicates", false),
TEXTURE("texture duplicates", false);

public final String label;
public final boolean filtered;

DuplicatesOption(String label, Boolean filtered) {
this.label = label;
this.filtered = filtered;
}
}

public enum ShadeAlignmentsOption {
NONE("none"),
Expand Down Expand Up @@ -169,7 +182,8 @@ public enum GroupOption {
LINKED("linked"),
PHASE("phase"),
REFERENCE_CONCORDANCE("reference concordance"),
MAPPING_QUALITY("mapping quality");
MAPPING_QUALITY("mapping quality"),
DUPLICATE("duplicate flag");

public final String label;
public final boolean reverse;
Expand Down Expand Up @@ -1221,6 +1235,7 @@ public static class RenderOptions implements Cloneable, Persistable {
private SortOption sortOption;
private GroupOption groupByOption;
private ShadeAlignmentsOption shadeAlignmentsOption;
private DuplicatesOption duplicatesOption;
private Integer mappingQualityLow;
private Integer mappingQualityHigh;
private boolean viewPairs = false;
Expand Down Expand Up @@ -1472,6 +1487,22 @@ public ShadeAlignmentsOption getShadeAlignmentsOption() {
}
}

public DuplicatesOption getDuplicatesOption() {
final IGVPreferences prefs = getPreferences();
if (duplicatesOption != null) {
return duplicatesOption;
} else {
duplicatesOption = prefs.getAsBoolean(SAM_FILTER_DUPLICATES)
? DuplicatesOption.FILTER
: DuplicatesOption.SHOW;
}
return duplicatesOption;
}

public void setDuplicatesOption(final DuplicatesOption duplicatesOption) {
this.duplicatesOption = duplicatesOption;
}

public int getMappingQualityLow() {
return mappingQualityLow == null ? getPreferences().getAsInt(SAM_SHADE_QUALITY_LOW) : mappingQualityLow;
}
Expand Down Expand Up @@ -1595,6 +1626,9 @@ public void marshalXML(Document document, Element element) {
if (shadeAlignmentsOption != null) {
element.setAttribute("shadeAlignmentsByOption", shadeAlignmentsOption.toString());
}
if (duplicatesOption != null) {
element.setAttribute("duplicatesOption", duplicatesOption.toString());
}
if (mappingQualityLow != null) {
element.setAttribute("mappingQualityLow", mappingQualityLow.toString());
}
Expand Down Expand Up @@ -1724,6 +1758,9 @@ public void unmarshalXML(Element element, Integer version) {
if (element.hasAttribute("shadeAlignmentsByOption")) {
shadeAlignmentsOption = ShadeAlignmentsOption.valueOf(element.getAttribute("shadeAlignmentsByOption"));
}
if (element.hasAttribute("duplicatesOption")) {
duplicatesOption = CollUtils.valueOf(DuplicatesOption.class, element.getAttribute("duplicatesOption"), null);
}
if (element.hasAttribute("mappingQualityLow")) {
mappingQualityLow = Integer.parseInt(element.getAttribute("mappingQualityLow"));
}
Expand Down Expand Up @@ -1798,6 +1835,7 @@ public void unmarshalXML(Element element, Integer version) {
minJunctionCoverage = Integer.parseInt(element.getAttribute("minJunctionCoverage"));
}
}

}


Expand Down
Loading
Loading