/*
* The MIT License (MIT)
*
* Copyright (c) 2007-2015 Broad Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.broad.igv.feature;
import org.apache.log4j.Logger;
import org.broad.igv.feature.genome.Genome;
import org.broad.igv.track.WindowFunction;
import java.util.*;
/**
* A convenience class providing default implementation for many IGVFeature
* methods.
*
* @author jrobinso
*/
public class BasicFeature extends AbstractFeature {
private static Logger log = Logger.getLogger(BasicFeature.class);
String representation;
protected List<Exon> exons;
protected int level = 1;
protected float score = Float.NaN;
protected float confidence;
String identifier;
private int thickEnd;
private int thickStart;
String[] parentIds;
String link;
public BasicFeature() {
}
public BasicFeature(String chr, int start, int end) {
this(chr, start, end, Strand.NONE);
this.thickStart = start;
this.thickEnd = end;
}
public BasicFeature(String chr, int start, int end, Strand strand) {
super(chr, start, end, strand);
this.thickStart = start;
this.thickEnd = end;
}
public BasicFeature(BasicFeature feature) {
super(feature.getChr(), feature.getStart(), feature.getEnd(), feature.getStrand());
super.setName(feature.getName());
this.confidence = feature.confidence;
this.color = feature.color;
this.description = feature.description;
this.exons = feature.exons;
this.level = feature.level;
this.score = feature.score;
this.identifier = feature.identifier;
this.type = feature.type;
this.link = feature.link;
this.thickStart = feature.thickStart;
this.thickEnd = feature.thickEnd;
this.attributes = feature.attributes;
}
public String getRepresentation() {
return representation;
}
public void setRepresentation(String representation) {
this.representation = representation;
}
/**
* @param identifier
*/
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public void setParentIds(String[] parentIds) {
this.parentIds = parentIds;
}
public String[] getParentIds() {
return parentIds;
}
@Override
public void setStart(int start) {
super.setStart(start);
this.thickStart = start;
}
@Override
public void setEnd(int end) {
super.setEnd(end);
this.thickEnd = end;
}
/**
* Defined in interface {@linkplain LocusScore}
*/
public String getValueString(double position, int mouseX, WindowFunction ignored) {
StringBuffer valueString = new StringBuffer();
String name = getName();
if (name != null) {
valueString.append("<b>" + name + "</b><br>");
}
valueString.append(getLocusString());
if (type != null && type.length() > 0) {
valueString.append("<br>Type = " + type);
}
if ((identifier != null) && ((name == null) || !name.equals(identifier))) {
valueString.append("<br>id = " + identifier);
}
if (!Float.isNaN(score)) {
valueString.append("<br>Score = " + score);
}
if (description != null) {
valueString.append("<br>" + description);
}
if (attributes != null) {
valueString.append(getAttributeString());
}
// Get exon number, if over an exon
int posZero = (int) position;
if (this.exons != null) {
for (Exon exon : exons) {
if (posZero >= exon.getStart() && posZero < exon.getEnd()) {
String exonString = exon.getValueString(position, mouseX, ignored);
if (exonString != null && exonString.length() > 0) {
valueString.append("<br>--------------<br>");
valueString.append(exonString);
}
}
}
}
return valueString.toString();
}
public void setScore(float score) {
this.score = score;
}
@Override
public float getScore() {
return score;
}
@Override
public List<Exon> getExons() {
return exons;
}
@Override
public boolean hasExons() {
return exons != null && exons.size() > 0;
}
/**
* Sort the exon collection, if any, by start position.
*/
public void sortExons() {
if (exons != null) {
Collections.sort(exons, new Comparator<IGVFeature>() {
public int compare(IGVFeature arg0, IGVFeature arg1) {
return arg0.getStart() - arg1.getStart();
}
});
}
}
public void addExon(Exon region) {
if (exons == null) {
exons = new ArrayList();
}
setStart(Math.min(getStart(), region.getStart()));
setEnd(Math.max(getEnd(), region.getEnd()));
exons.add(region);
}
/**
* Add a UTR or CDS feature from a GFF or EMBL/NCBI type format. If the feature overlaps an existing exon merge
* the two, otherwise create a new one.
*
* @param bf
*/
public void addUTRorCDS(BasicFeature bf) {
boolean found = false;
if (exons == null) {
exons = new ArrayList();
}
final String exonType = bf.getType();
for (Exon exon : exons) {
if (exon.contains(bf)) {
// Replace exon attributes with coding features. Perhaps in the future we will merge them.
exon.setAttributes(bf.getAttributes());
if (SequenceOntology.cdsTypes.contains(exonType)) {
exon.setNonCoding(false);
exon.setCodingStart(bf.getStart());
exon.setCodingEnd(bf.getEnd());
} else if (SequenceOntology.utrTypes.contains(exonType)) {
exon.setNonCoding(true);
boolean rhs =
(SequenceOntology.fivePrimeUTRTypes.contains(exonType) && bf.getStrand() == Strand.POSITIVE) ||
(SequenceOntology.threePrimeUTRTypes.contains(exonType) && bf.getStrand() == Strand.NEGATIVE);
if (rhs) {
exon.setCodingStart(bf.getEnd());
exon.setCodingEnd(bf.getEnd());
} else {
exon.setCodingEnd(bf.getStart());
exon.setCodingStart(bf.getStart());
}
}
found = true;
break;
}
}
if (!found) {
// No match
final Exon exon = new Exon(bf);
exon.setNonCoding(!SequenceOntology.cdsTypes.contains(bf.getType()));
addExon(exon);
}
}
@Override
public String getIdentifier() {
return identifier;
}
public int getExonCount() {
return (exons == null) ? 0 : exons.size();
}
public void setURL(String link) {
this.link = link;
}
public String getURL() {
return link;
}
public int getThickEnd() {
return thickEnd;
}
public void setThickEnd(int thickEnd) {
this.thickEnd = thickEnd;
}
public int getThickStart() {
return thickStart;
}
public void setThickStart(int thickStart) {
this.thickStart = thickStart;
}
/**
* @param genome
* @param proteinPosition 1-Indexed position of protein
* @return
*/
public Codon getCodon(Genome genome, int proteinPosition) {
// Nucleotide position on the coding portion of the transcript (the untranslated messenger RNA)
int startTranscriptPosition = (proteinPosition - 1) * 3;
int[] featurePositions = new int[]{startTranscriptPosition, startTranscriptPosition + 1,
startTranscriptPosition + 2};
int[] genomePositions = featureToGenomePosition(featurePositions);
Codon codonInfo = new Codon(getChr(), proteinPosition, getStrand());
for (int gp : genomePositions) {
codonInfo.setNextGenomePosition(gp);
}
if (!codonInfo.isGenomePositionsSet()) {
//Protein position invalid, could not find genomic sequence
return null;
}
codonInfo.calcSequence(genome);
AminoAcid aa = AminoAcidManager.getInstance().getAminoAcid(codonInfo.getSequence());
if (aa != null) {
codonInfo.setAminoAcid(aa);
return codonInfo;
} else {
return null;
}
}
/**
* Convert a series of feature positions into genomic positions.
*
* @param featurePositions Must be 0-based.
* @return Positions relative to genome (0-based). Will contain "-1"s for
* positions not found. Sorted ascending for positive strand,
* descending for negative strand.
*/
int[] featureToGenomePosition(int[] featurePositions) {
List<Exon> exons = getExons();
int[] genomePositions = new int[featurePositions.length];
Arrays.fill(genomePositions, -1);
if (exons != null) {
if (getStrand() == Strand.NONE) {
throw new IllegalStateException("Exon not on a strand");
}
boolean positive = getStrand() == Strand.POSITIVE;
/*
We loop over all exons, either from the beginning or the end.
Increment position only on coding regions.
*/
int genomePosition, posIndex = 0, all_exon_counter = 0;
int current_exon_end = 0;
Exon exon;
for (int exnum = 0; exnum < exons.size(); exnum++) {
if (positive) {
exon = exons.get(exnum);
} else {
exon = exons.get(exons.size() - 1 - exnum);
}
int exon_length = exon.getCodingLength();
genomePosition = positive ? exon.getCdStart() : exon.getCdEnd() - 1;
current_exon_end += exon_length;
int incr = positive ? 1 : -1;
int interval;
while (featurePositions[posIndex] < current_exon_end) {
//Position of interest is on this exon
//Can happen up to exon_length times
interval = featurePositions[posIndex] - all_exon_counter;
all_exon_counter = featurePositions[posIndex];
genomePosition += interval * incr;
genomePositions[posIndex] = genomePosition;
posIndex++;
if (posIndex >= featurePositions.length) {
return genomePositions;
}
}
//No more positions of interest on this exon
//move up counter to end of exon
all_exon_counter = current_exon_end;
}
}
return genomePositions;
}
}