Skip to content

Commit

Permalink
Update with many changes to improve supplemental alignment display an…
Browse files Browse the repository at this point in the history
…d navigation.

Major rework of the previous alignment dialog popup to make it interactive and clearer.
Use it to navigate between reads in a supplemenary alignment group.
   Click to move to the selected read.
   Shift + Click to add it as a split screen.

When jumping to a selected read/mate the selected reads will be now be sorted to the top.

Renamed several classes and moved them to the ui/supdiagram package
Refactoring some methods around Frame creation, this could still be improved.
  • Loading branch information
lbergelson committed Nov 15, 2023
1 parent 175f234 commit b57c9ed
Show file tree
Hide file tree
Showing 18 changed files with 895 additions and 458 deletions.
1 change: 1 addition & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
exports org.broad.igv.logging;
exports org.broad.igv.util.liftover;
exports org.broad.igv.sam.smrt;
exports org.broad.igv.ui.supdiagram;

requires com.google.common;
requires commons.math3;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/broad/igv/jbrowse/Chord.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public static List<Chord> fromSAString(Alignment a) {
c.refName = shortName(a.getChr());
c.start = a.getStart();
c.end = a.getEnd();
c.mate = new Mate(shortName(sa.chr), sa.start, sa.start + sa.getLenOnRef());
c.mate = new Mate(shortName(sa.chr), sa.start, sa.start + sa.getLengthOnReference());
chords.add(c);
}
}
Expand Down
19 changes: 7 additions & 12 deletions src/main/java/org/broad/igv/lists/GeneList.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
package org.broad.igv.lists;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

Expand All @@ -43,7 +42,11 @@ public class GeneList {
private List<String> loci;

public GeneList(String name, String description, String group, List<String> loci) {
init(name, description, group, loci);
this.group = group;
this.description = description;
this.name = name;
//We do this to guarantee that certain operations will be supported
this.loci = loci;
}

public GeneList(String name, List<String> loci) {
Expand All @@ -54,14 +57,6 @@ public GeneList() {
this.group = GeneListManager.USER_GROUP;
}

private void init(String name, String description, String group, List<String> loci) {
this.group = group;
this.description = description;
this.name = name;
//We do this to guarantee that certain operations will be supported
this.loci = loci;
}

public String getName() {
return name;
}
Expand All @@ -76,13 +71,13 @@ public int size() {

public void add(String gene) {
if (loci == null) {
loci = new ArrayList<String>(1);
loci = new ArrayList<>(1);
}
try {
//Can't guarantee that list will support this operation
loci.add(gene);
} catch (Exception e) {
loci = new ArrayList<String>(loci);
loci = new ArrayList<>(loci);
loci.add(gene);
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/broad/igv/sam/AlignmentDataManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ public AlignmentInterval getLoadedInterval(ReferenceFrame frame) {
}

public AlignmentInterval getLoadedInterval(ReferenceFrame frame, boolean includeOverlaps) {
// Search for interval completely containining reference frame region
// Search for interval completely containing reference frame region
for (AlignmentInterval interval : intervalCache) {
if (interval.contains(frame.getCurrentRange())) {
return interval;
Expand Down Expand Up @@ -359,8 +359,10 @@ public void load(ReferenceFrame frame,
currentlyLoading = null;
}


IGVEventBus.getInstance().post(new DataLoadedEvent(frame));


}

/**
Expand Down
16 changes: 8 additions & 8 deletions src/main/java/org/broad/igv/sam/AlignmentRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -944,10 +944,10 @@ private static void drawClippedEnds(final Graphics2D g, final int[] xPoly, final
try {
//left side
if (drawLeftClip) {
if (sri != null && sri.previous != null) {
final SupplementaryAlignment previous = sri.previous;
if (previous.contigsMatch(sri.alignment)) {
g.setColor(previous.getStrand() == sri.alignment.getReadStrand() ? NON_INVERSION_COLOR : INVERSION_COLOR);
if (sri != null && sri.previous() != null) {
final SupplementaryAlignment previous = sri.previous();
if (previous.contigsMatch(sri.alignment())) {
g.setColor(previous.getStrand() == sri.alignment().getReadStrand() ? NON_INVERSION_COLOR : INVERSION_COLOR);
} else {
if (previous.getContig() != null) {
g.setColor(ChromosomeColors.getColor(previous.getContig()));
Expand All @@ -969,11 +969,11 @@ private static void drawClippedEnds(final Graphics2D g, final int[] xPoly, final
}
//right side
if (drawRightClip) {
if (sri != null && sri.next != null) {
final SupplementaryAlignment next = sri.next;
if (next.contigsMatch(sri.alignment)) {
if (sri != null && sri.next() != null) {
final SupplementaryAlignment next = sri.next();
if (next.contigsMatch(sri.alignment())) {
//TODO Set to scaled color by contig position
g.setColor(next.getStrand() == sri.alignment.getReadStrand() ? NON_INVERSION_COLOR : INVERSION_COLOR);
g.setColor(next.getStrand() == sri.alignment().getReadStrand() ? NON_INVERSION_COLOR : INVERSION_COLOR);
} else {
g.setColor(ChromosomeColors.getColor(next.getContig()));
rightContigLabel = next.getContig();
Expand Down
31 changes: 21 additions & 10 deletions src/main/java/org/broad/igv/sam/AlignmentTrack.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.broad.igv.ui.panel.IGVPopupMenu;
import org.broad.igv.ui.panel.ReferenceFrame;
import org.broad.igv.ui.util.MessageUtils;
import org.broad.igv.ui.util.UIUtilities;
import org.broad.igv.util.ResourceLocator;
import org.broad.igv.util.StringUtils;
import org.broad.igv.util.blat.BlatClient;
Expand Down Expand Up @@ -83,6 +84,14 @@ public class AlignmentTrack extends AbstractTrack implements IGVEventObserver {
// Alignment colors
static final Color DEFAULT_ALIGNMENT_COLOR = new Color(185, 185, 185); //200, 200, 200);

public static void sortSelectedReadsToTheTop(final Set<String> selectedReadNames) {
//copy this in case it changes out from under us
Set<String> selectedReadNameCopy = new HashSet<>(selectedReadNames);
//Run this on the event thread to make sure it happens after loading begins
UIUtilities.invokeOnEventThread(() ->
IGV.getInstance().sortAlignmentTracks(SortOption.NONE, null, null, false, selectedReadNameCopy));
}

public enum ColorOption {
INSERT_SIZE,
READ_STRAND,
Expand Down Expand Up @@ -189,7 +198,7 @@ enum OrientationType {
private static final int GROUP_MARGIN = 5;
private static final int TOP_MARGIN = 20;
private static final int DS_MARGIN_0 = 2;
private static final int DOWNAMPLED_ROW_HEIGHT = 3;
private static final int DOWNSAMPLED_ROW_HEIGHT = 3;
private static final int INSERTION_ROW_HEIGHT = 9;

public enum BisulfiteContext {
Expand Down Expand Up @@ -382,11 +391,13 @@ public void receiveEvent(IGVEvent event) {
}
case REFRESH -> repaint();
}
} else if (event instanceof final DataLoadedEvent dataLoaded) {
actionToPerformOnFrameLoad.computeIfPresent(dataLoaded.referenceFrame(), (k, v) -> {
v.accept(k);
return null;
});
} else if (event instanceof DataLoadedEvent dataLoaded) {
if (dataManager.isLoaded(dataLoaded.referenceFrame())) {
actionToPerformOnFrameLoad.computeIfPresent(dataLoaded.referenceFrame(), (k, v) -> {
v.accept(k);
return null; //remove this action from the map
});
}
}
}

Expand Down Expand Up @@ -468,7 +479,7 @@ public int getHeight() {

int nGroups = dataManager.getMaxGroupCount();
int h = Math.max(minHeight, getNLevels() * getRowHeight() + nGroups * GROUP_MARGIN + TOP_MARGIN
+ DS_MARGIN_0 + DOWNAMPLED_ROW_HEIGHT);
+ DS_MARGIN_0 + DOWNSAMPLED_ROW_HEIGHT);
return Math.max(minimumHeight, h);
}

Expand Down Expand Up @@ -535,7 +546,7 @@ public void render(RenderContext context, Rectangle rect) {
rect.y += DS_MARGIN_0;

downsampleRect = new Rectangle(rect);
downsampleRect.height = DOWNAMPLED_ROW_HEIGHT;
downsampleRect.height = DOWNSAMPLED_ROW_HEIGHT;
renderDownsampledIntervals(context, downsampleRect);

alignmentsRect = new Rectangle(rect);
Expand Down Expand Up @@ -687,7 +698,7 @@ private void renderAlignments(RenderContext context, Rectangle inputRect) {
public void renderExpandedInsertion(InsertionMarker insertionMarker, RenderContext context, Rectangle inputRect) {

boolean leaveMargin = getDisplayMode() != DisplayMode.SQUISHED;
inputRect.y += DS_MARGIN_0 + DOWNAMPLED_ROW_HEIGHT + DS_MARGIN_0;
inputRect.y += DS_MARGIN_0 + DOWNSAMPLED_ROW_HEIGHT + DS_MARGIN_0;

final AlignmentInterval loadedInterval = dataManager.getLoadedInterval(context.getReferenceFrame(), true);
PackedAlignments groups = dataManager.getGroups(loadedInterval, renderOptions);
Expand Down Expand Up @@ -904,7 +915,7 @@ public boolean handleDataClick(TrackClickEvent te) {
return false;
}

void setSelectedAlignment(Alignment alignment) {
public void setSelectedAlignment(Alignment alignment) {
Color c = readNamePalette.get(alignment.getReadName());
selectedReadNames.put(alignment.getReadName(), c);
}
Expand Down
73 changes: 7 additions & 66 deletions src/main/java/org/broad/igv/sam/AlignmentTrackMenu.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
package org.broad.igv.sam;

import htsjdk.samtools.SAMTag;
import htsjdk.samtools.util.Locatable;
import org.broad.igv.Globals;
import org.broad.igv.feature.Locus;
import org.broad.igv.feature.Range;
import org.broad.igv.feature.Strand;
import org.broad.igv.jbrowse.CircularViewUtilities;
import org.broad.igv.lists.GeneList;
import org.broad.igv.logging.LogManager;
import org.broad.igv.logging.Logger;
import org.broad.igv.prefs.Constants;
import org.broad.igv.prefs.PreferencesManager;
import org.broad.igv.sam.mods.BaseModficationFilter;
import org.broad.igv.sam.mods.BaseModificationKey;
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;
import org.broad.igv.track.Track;
import org.broad.igv.track.TrackClickEvent;
import org.broad.igv.track.TrackMenuUtils;
import org.broad.igv.ui.AlignmentDiagramFrame;
import org.broad.igv.ui.supdiagram.SupplementaryAlignmentDiagramDialog;
import org.broad.igv.ui.IGV;
import org.broad.igv.ui.InsertSizeSettingsDialog;
import org.broad.igv.ui.panel.FrameManager;
Expand Down Expand Up @@ -203,7 +201,7 @@ private void addShowChimericRegions(final AlignmentTrack alignmentTrack, final T
try {
List<SupplementaryAlignment> supplementaryAlignments = SupplementaryAlignment.parseFromSATag(saTag);
alignmentTrack.setSelectedAlignment(clickedAlignment);
addNewLociToFrames(e.getFrame(), supplementaryAlignments, alignmentTrack.getSelectedReadNames().keySet());
FrameManager.addNewLociToFrames(e.getFrame(), supplementaryAlignments, alignmentTrack.getSelectedReadNames().keySet());
} catch (final Exception ex) {
MessageUtils.showMessage("Failed to handle SA tag: " + saTag + " due to " + ex.getMessage());
item.setEnabled(false);
Expand All @@ -221,7 +219,7 @@ private void addShowDiagram(final TrackClickEvent e, final Alignment clickedAlig
item.setEnabled(true);
item.addActionListener(aEvt -> {
try {
final AlignmentDiagramFrame frame = new AlignmentDiagramFrame(clickedAlignment, new Dimension(500, 100));
final SupplementaryAlignmentDiagramDialog frame = new SupplementaryAlignmentDiagramDialog(IGV.getInstance().getMainFrame(), clickedAlignment, new Dimension(500, 250));
frame.setVisible(true);
} catch (final Exception ex) {
MessageUtils.showMessage("Failed to handle SA tag: " + clickedAlignment.getAttribute(SAMTag.SA.name()) + " due to " + ex.getMessage());
Expand Down Expand Up @@ -683,7 +681,7 @@ void addColorByMenuItem() {
// Base modifications
JRadioButtonMenuItem bmMenuItem;

Set<String> allModifications = dataManager.getAllBaseModificationKeys().stream().map(bmKey -> bmKey.getModification()).collect(Collectors.toSet());
Set<String> allModifications = dataManager.getAllBaseModificationKeys().stream().map(BaseModificationKey::getModification).collect(Collectors.toSet());
if (allModifications.size() > 0) {
BaseModficationFilter filter = renderOptions.getBasemodFilter();
boolean groupByStrand = alignmentTrack.getPreferences().getAsBoolean(BASEMOD_GROUP_BY_STRAND);
Expand Down Expand Up @@ -1228,7 +1226,7 @@ private void gotoMate(final ReferenceFrame frame, Alignment alignment) {
int newStart = (int) Math.max(0, (start + (alignment.getEnd() - alignment.getStart()) / 2 - range / 2));
int newEnd = newStart + (int) range;
frame.jumpTo(chr, newStart, newEnd);
sortSelectedReadsToTheTop(alignmentTrack.getSelectedReadNames().keySet());
AlignmentTrack.sortSelectedReadsToTheTop(alignmentTrack.getSelectedReadNames().keySet());
frame.recordHistory();
} else {
MessageUtils.showMessage("Alignment does not have mate, or it is not mapped.");
Expand All @@ -1246,70 +1244,13 @@ private void splitScreenMate(ReferenceFrame frame, Alignment alignment) {
ReadMate mate = alignment.getMate();
if (mate != null && mate.isMapped()) {
alignmentTrack.setSelectedAlignment(alignment);
addNewLociToFrames(frame, List.of(mate), alignmentTrack.getSelectedReadNames().keySet());
FrameManager.addNewLociToFrames(frame, List.of(mate), alignmentTrack.getSelectedReadNames().keySet());
} else {
MessageUtils.showMessage("Alignment does not have mate, or it is not mapped.");
}
}
}

private static void addNewLociToFrames(final ReferenceFrame frame, final List<? extends Locatable> toIncludeInSplit, final Set<String> selectedReadNames) {
final List<String> newLoci = toIncludeInSplit.stream()
.map(locatable -> getLocusStringForAlignment(frame, locatable))
.collect(Collectors.toList());
List<String> loci = createLociList(frame, newLoci);
String listName = String.join(" ", loci); // TODO check the trailing " " was unnecessary
//Need to sort the frames by position
GeneList geneList = new GeneList(listName, loci);
geneList.sort(Comparator.comparing(Locus::fromString, SortOption.POSITION_COMPARATOR));
IGV.getInstance().getSession().setCurrentGeneList(geneList);
IGV.getInstance().resetFrames();

/*
We want the sort to happen after the frame refresh / track loading begins.
This puts the sort onto the event thread so that it happens after loading has already started.
Since loading reads happens asynchronously on a different thread from the event thread, it is likely
that the loading won't be done by the time the sort fires. In that case the sort will be set as the
action to perform when the load is finished
See {@link AlignmentTrack#sortRows(SortOption, Double, String, boolean, Set)}
*/
sortSelectedReadsToTheTop(selectedReadNames);
}

private static void sortSelectedReadsToTheTop(final Set<String> selectedReadNames) {
//copy this in case it changes out from under us
Set<String> selectedReadNameCopy = new HashSet<>(selectedReadNames);
UIUtilities.invokeOnEventThread(() ->
IGV.getInstance().sortAlignmentTracks(SortOption.NONE, null, null, false, selectedReadNameCopy));
}

private static List<String> createLociList(final ReferenceFrame frame, final List<String> lociToAdd) {
final List<String> loci = new ArrayList<>(FrameManager.getFrames().size() + lociToAdd.size());
if (FrameManager.isGeneListMode()) {
for (ReferenceFrame ref : FrameManager.getFrames()) {
//If the frame-name is a locus, we use it unaltered
//Don't want to reprocess, easy to get off-by-one
String name = ref.getName();
loci.add(Locus.fromString(name) != null ? name : ref.getFormattedLocusString());
}
} else {
loci.add(frame.getFormattedLocusString());
}
loci.addAll(lociToAdd);
return loci;
}

private static String getLocusStringForAlignment(final ReferenceFrame frame, final Locatable alignment) {
int adjustedMateStart = alignment.getStart() - 1;

// Generate a locus string for the alignment. Keep the window width (in base pairs) == to the current range
Range range = frame.getCurrentRange();
int length = range.getLength();
int start = Math.max(0, adjustedMateStart - length / 2);
int end = start + length;
return Locus.getFormattedLocusString(alignment.getContig(), start, end);
}


/**
* Get the most "specific" alignment at the specified location. Specificity refers to the smallest alignment
Expand Down
11 changes: 5 additions & 6 deletions src/main/java/org/broad/igv/sam/SAMAlignment.java
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ private String getMlTagString(SAMRecord.SAMTagAndValue tag) {

private String getSupplAlignmentString() {
StringBuilder sb = new StringBuilder();
sb.append("SupplementaryAlignments");
sb.append("Supplementary Alignments");
final List<SupplementaryAlignment> supplementaryAlignments = getSupplementaryAlignments();
final int insertionIndex = SupplementaryAlignment.getInsertionIndex(this, supplementaryAlignments);
int i = 0;
Expand All @@ -996,14 +996,13 @@ private String getSupplAlignmentString() {
}

private String getThisReadDescriptionForSAList() {
return "<br>" +
"<b>"
return "<br>"
+ "<b>"
+ chr + ":" + Globals.DECIMAL_FORMAT.format(getAlignmentStart() + 1) + "-" + Globals.DECIMAL_FORMAT.format(getAlignmentEnd())
+ " (" + getReadStrand().toShortString() + ")" +
"</b>";
+ " (" + this.getReadStrand().toShortString() + ") = " + Globals.DECIMAL_FORMAT.format(getLengthOnReference()) + "bp @MAPQ " + this.getMappingQuality() + " NM" + getAttribute(SAMTag.NM)
+ "</b>";
}


public float getScore() {
return getMappingQuality();
}
Expand Down
Loading

0 comments on commit b57c9ed

Please sign in to comment.