Skip to content

Commit

Permalink
Searchable bigbed (#1447)
Browse files Browse the repository at this point in the history
Add support for searching by bb extra indexes, including trix
  • Loading branch information
jrobinso authored Nov 28, 2023
1 parent c5114a9 commit cb9cd9b
Show file tree
Hide file tree
Showing 26 changed files with 337 additions and 225 deletions.
12 changes: 7 additions & 5 deletions src/main/java/org/broad/igv/feature/genome/Genome.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ private void addTracks(GenomeConfig config) {
res.setVisibilityWindow(vw);
}

if (trackConfig.searchTrix != null) {
res.setTrixURL(trackConfig.searchTrix);
}

res.setFeatureInfoURL(trackConfig.infoURL);
Boolean indexed = trackConfig.indexed;
if (indexed != null) {
Expand Down Expand Up @@ -351,7 +355,7 @@ public Genome(String id, List<Chromosome> chromosomes) {
* Return the canonical chromosome name for the (possibly) alias
*
* @param str chromosome or alias name
* @return the canonical chromsoome name -- i.e. chromosome name as defined by the reference sequence
* @return the canonical chromsoome name -if the chromosome exists.
*/
public String getCanonicalChrName(String str) {
if (str == null) {
Expand All @@ -361,9 +365,7 @@ public String getCanonicalChrName(String str) {
} else if (chromAliasSource != null) {
try {
ChromAlias aliasRecord = chromAliasSource.search(str);
if (aliasRecord == null) {
return str;
} else {
if (aliasRecord != null) {
String chr = aliasRecord.getChr();
chrAliasCache.put(str, chr);
return chr;
Expand Down Expand Up @@ -487,9 +489,9 @@ public Chromosome getChromosome(String name) {
if (chromosomeMap.containsKey(chrName)) {
return chromosomeMap.get(chrName);
} else {
int idx = this.chromosomeMap.size();
int length = this.sequence.getChromosomeLength(chrName);
if (length > 0) {
int idx = this.chromosomeMap.size();
Chromosome chromosome = new Chromosome(idx, chrName, length);
chromosomeMap.put(chrName, chromosome);
return chromosome;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.broad.igv.ui.PanelName;
import org.broad.igv.ui.commandbar.GenomeListManager;
import org.broad.igv.ui.panel.FrameManager;
import org.broad.igv.ui.panel.ReferenceFrame;
import org.broad.igv.ui.util.MessageUtils;
import org.broad.igv.ui.util.ProgressBar;
import org.broad.igv.ui.util.ProgressMonitor;
Expand All @@ -72,6 +73,7 @@
import java.net.SocketException;
import java.net.URL;
import java.net.URLDecoder;
import java.sql.Ref;
import java.util.*;
import java.util.List;

Expand Down Expand Up @@ -231,8 +233,9 @@ public Genome loadGenome(String genomePath, ProgressMonitor monitor) throws IOEx

// hasInstance() test needed for unit tests
if (IGV.hasInstance()) {
IGV.getInstance().goToLocus(newGenome.getDefaultPos());
IGV.getInstance().goToLocus(newGenome.getHomeChromosome()); // newGenome.getDefaultPos());
loadGenomeAnnotations(newGenome);
IGV.getInstance().resetFrames();
}

if (PreferencesManager.getPreferences().getAsBoolean(Constants.CIRC_VIEW_ENABLED) && CircularViewUtilities.ping()) {
Expand Down
19 changes: 16 additions & 3 deletions src/main/java/org/broad/igv/feature/genome/Sequence.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,26 @@
*/
public interface Sequence {

byte[] getSequence(String chr, int start, int end);
/**
* Return the sequence for the given range. If sequence named "seq" does not exist returns null.
* @param chr
* @param start
* @param end
* @return The sequence in bytes, or null if no sequence exists
*/
byte[] getSequence(String seq, int start, int end) ;

byte getBase(String chr, int position);
byte getBase(String seq, int position);

List<String> getChromosomeNames();

int getChromosomeLength(String chrname);
/**
* Return the given sequence length. If no sequence exists with name "seq" return -1.
*
* @param seq
* @return
*/
int getChromosomeLength(String seq);

List<Chromosome> getChromosomes();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.broad.igv.feature.genome;

public class SequenceNotFoundException extends RuntimeException {
public SequenceNotFoundException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public List<String> getChromosomeNames() {

@Override
public int getChromosomeLength(String chrname) {
return index.getSequenceSize(chrname);
return index.getSequenceSize(chrname);
}

@Override
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/broad/igv/session/History.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.broad.igv.ui.IGV;
import org.broad.igv.ui.action.SearchCommand;
import org.broad.igv.ui.panel.FrameManager;
import org.broad.igv.util.LongRunningTask;

import java.util.ArrayList;
import java.util.LinkedList;
Expand Down Expand Up @@ -121,7 +122,7 @@ public void processItem(Entry entry) {
if (FrameManager.isGeneListMode()) {
IGV.getInstance().setGeneList(null, false);
}
(new SearchCommand(FrameManager.getDefaultFrame(), locus, false)).execute();
LongRunningTask.submit(new SearchCommand(FrameManager.getDefaultFrame(), locus, false));
//Zoom should be implicit in the locus
//FrameManager.getDefaultFrame().setZoom(entry.getZoom());
}
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/broad/igv/track/FeatureSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package org.broad.igv.track;

import htsjdk.tribble.Feature;
import htsjdk.tribble.NamedFeature;
import org.broad.igv.feature.LocusScore;
import org.broad.igv.ui.panel.ReferenceFrame;

Expand Down Expand Up @@ -92,4 +93,17 @@ default void close() {
default Object getHeader() {
return null;
}

/**
* Return true if the source can be searched for a feature by name
*
* @return
*/
default boolean isSearchable() {
return false;
}

default NamedFeature search(String name) {
return null;
}
}
11 changes: 11 additions & 0 deletions src/main/java/org/broad/igv/track/FeatureTrack.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package org.broad.igv.track;

import htsjdk.tribble.Feature;
import htsjdk.tribble.NamedFeature;
import htsjdk.tribble.TribbleException;
import org.broad.igv.event.IGVEvent;
import org.broad.igv.logging.*;
Expand Down Expand Up @@ -1056,6 +1057,16 @@ public void marshalXML(Document document, Element element) {

}

@Override
public boolean isSearchable() {
return source.isSearchable();
}

@Override
public NamedFeature search(String token) {
return source.search(token);
}

@Override
public void unmarshalXML(Element element, Integer version) {

Expand Down
21 changes: 19 additions & 2 deletions src/main/java/org/broad/igv/track/Track.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@


import htsjdk.tribble.Feature;
import htsjdk.tribble.NamedFeature;
import org.broad.igv.renderer.ContinuousColorScale;
import org.broad.igv.renderer.DataRange;
import org.broad.igv.renderer.Renderer;
Expand Down Expand Up @@ -272,9 +273,12 @@ default Color getExplicitAltColor() {

void setAutoScale(boolean autoScale);

default boolean isShowFeatureNames() {return true;}
default boolean isShowFeatureNames() {
return true;
}

default void setShowFeatureNames(boolean b) {}
default void setShowFeatureNames(boolean b) {
}

/**
* Return the java property or attribute for the feature display name. Default is "null", in which case the
Expand All @@ -286,6 +290,19 @@ default String getLabelField() {
return null;
}

/**
* Return true if the track can be searched for a feature by name.
*
* @return
*/
default boolean isSearchable() {
return false;
}

default NamedFeature search(String token) {
return null;
}

default void repaint() {
IGV.getInstance().repaint(this);
}
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/org/broad/igv/track/TrackLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
import org.broad.igv.sam.reader.IndexNotFoundException;
import org.broad.igv.tdf.TDFDataSource;
import org.broad.igv.tdf.TDFReader;
import org.broad.igv.ucsc.Trix;
import org.broad.igv.ucsc.bb.BBDataSource;
import org.broad.igv.ucsc.bb.BBFeatureSource;
import org.broad.igv.ucsc.bb.BBFile;
Expand Down Expand Up @@ -792,9 +793,9 @@ public void loadBWFile(ResourceLocator locator, List<Track> newTracks, Genome ge
String trackId = locator.getPath();

String path = locator.getPath();
BBFile reader = new BBFile(path, genome);
Track track = null;

String trixURL = locator.getTrixURL();
BBFile reader = trixURL == null ? new BBFile(path, genome) : new BBFile(path, genome, trixURL);
Track track;
if (reader.isBigWigFile()) {
BBDataSource bigwigSource = new BBDataSource(reader, genome);
track = new DataSourceTrack(locator, trackId, trackName, bigwigSource);
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/broad/igv/ucsc/Trix.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ String _getBuffer(String searchWord) throws IOException {
int end = 65536;
List<IndexEntry> indexes = this.getIndex();
for (IndexEntry entry : indexes) {
String trimmedKey = entry.key.substring(0, searchWord.length());
int trimEnd = Math.min(entry.key.length(), searchWord.length());
String trimmedKey = entry.key.substring(0, trimEnd);
if (trimmedKey.compareTo(searchWord) < 0) {
start = entry.value;
end = entry.value + 65536;
Expand Down
28 changes: 20 additions & 8 deletions src/main/java/org/broad/igv/ucsc/bb/BBFeatureSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@

package org.broad.igv.ucsc.bb;

import htsjdk.tribble.NamedFeature;
import org.broad.igv.feature.BasicFeature;
import org.broad.igv.feature.LocusScore;
import org.broad.igv.feature.genome.Genome;
import org.broad.igv.logging.LogManager;
import org.broad.igv.logging.Logger;
import org.broad.igv.track.FeatureSource;
import org.broad.igv.track.WindowFunction;

Expand All @@ -42,10 +45,9 @@
*/
public class BBFeatureSource implements FeatureSource {

final int screenWidth = 1000; // TODO use actual screen width
private static Logger log = LogManager.getLogger(BBFeatureSource.class);
private final Genome genome;


Collection<WindowFunction> availableWindowFunctions =
Arrays.asList(WindowFunction.min, WindowFunction.mean, WindowFunction.max, WindowFunction.none);

Expand All @@ -56,9 +58,6 @@ public class BBFeatureSource implements FeatureSource {

private Map<WindowFunction, List<LocusScore>> wholeGenomeScores;

// Lookup table to support chromosome aliasing.
private Map<String, String> chrNameMap = new HashMap();

public BBFeatureSource(BBFile reader, Genome genome) throws IOException {

super();
Expand Down Expand Up @@ -108,6 +107,20 @@ public List<LocusScore> getCoverageScores(String chr, int start, int end, int zo
return null;
}

public boolean isSearchable() {
return reader.isSearchable();
}

@Override
public NamedFeature search(String term) {
try {
return reader.search(term);
} catch (IOException e) {
log.error("Error searching for: " + term, e);
return null;
}
}

static class FeatureIterator implements Iterator<BasicFeature> {

List<BasicFeature> features;
Expand All @@ -125,10 +138,9 @@ public FeatureIterator(List<BasicFeature> features, int start, int end) {
}

private void advance() {
if(idx == features.size()) {
if (idx == features.size()) {
next = null;
}
else {
} else {
while (idx < features.size()) {
next = features.get(idx++);
if (next.getStart() > end) {
Expand Down
27 changes: 26 additions & 1 deletion src/main/java/org/broad/igv/ucsc/bb/BBFile.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.broad.igv.ucsc.bb;

import htsjdk.samtools.seekablestream.SeekableStream;
import htsjdk.tribble.NamedFeature;
import org.broad.igv.data.BasicScore;
import org.broad.igv.feature.BasicFeature;
import org.broad.igv.feature.LocusScore;
Expand Down Expand Up @@ -112,6 +113,9 @@ public Set<String> getChromosomeNames() {
return chrNames;
}

public void setTrix(Trix trix) {
}


enum Type {BIGWIG, BIGBED}

Expand Down Expand Up @@ -384,6 +388,9 @@ BBZoomHeader zoomLevelForScale(double bpPerPixel) {
return lastLevel.reductionLevel / 2 < bpPerPixel ? lastLevel : null;
}

public boolean isSearchable() {
return header.extraIndexCount > 0;
}

/**
* Search the extended BP tree for the search term, and return any matching features. This only works
Expand All @@ -394,6 +401,7 @@ BBZoomHeader zoomLevelForScale(double bpPerPixel) {
* @param term
* @returns {Promise<void>}
*/

public BasicFeature search(String term) throws IOException {

if (this.header == null) {
Expand All @@ -403,6 +411,16 @@ public BasicFeature search(String term) throws IOException {
return null;
}

if (this.trix != null) {
String termLower = term.toLowerCase();
Map<String, String[]> results = trix.search(termLower);
if (results != null) {
String[] exactMatches = results.get(termLower);
if (exactMatches.length > 0) term = exactMatches[0];
}
}


long[] region = this.searchForRegions(term); // Either 1 or no (undefined) reginos returned for now
if (region != null) {
long start = region[0];
Expand All @@ -412,11 +430,18 @@ public BasicFeature search(String term) throws IOException {
is.seek(start);
is.readFully(buffer);
List<BasicFeature> features = decodeFeatures(buffer, -1, -1, -1);
BasicFeature largest = features.stream().reduce((f1, f2) -> {

// Filter features to those matching term
final String searchTerm = term;

BasicFeature largest = features.stream().filter(f -> {
return f.getName().equalsIgnoreCase(searchTerm) || f.getAttributes().values().stream().anyMatch(v -> v.equalsIgnoreCase(searchTerm));
}).reduce((f1, f2) -> {
int l1 = f1.getEnd() - f1.getStart();
int l2 = f2.getEnd() - f2.getStart();
return l1 > l2 ? f1 : f2;
}).get();

return largest;
}
}
Expand Down
Loading

0 comments on commit cb9cd9b

Please sign in to comment.