X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fio%2FFeaturesFile.java;h=a69788b8bdb1f627822d39ca62ede0cf18b484e1;hb=bc455f675d56d49d0a0f132809d8117f7ffca5ab;hp=39ec10de7b9e8c0ee03972a1d624baf17420240e;hpb=84ed42e8d0aa07b03441b045d240ebb51f94c53c;p=jalview.git diff --git a/src/jalview/io/FeaturesFile.java b/src/jalview/io/FeaturesFile.java index 39ec10d..a69788b 100755 --- a/src/jalview/io/FeaturesFile.java +++ b/src/jalview/io/FeaturesFile.java @@ -29,11 +29,13 @@ import jalview.api.FeaturesSourceI; import jalview.datamodel.AlignedCodonFrame; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; +import jalview.datamodel.MappedFeatures; import jalview.datamodel.SequenceDummy; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.datamodel.features.FeatureMatcherSet; import jalview.datamodel.features.FeatureMatcherSetI; +import jalview.gui.Desktop; import jalview.io.gff.GffHelperBase; import jalview.io.gff.GffHelperFactory; import jalview.io.gff.GffHelperI; @@ -49,9 +51,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.TreeMap; /** * Parses and writes features files, which may be in Jalview, GFF2 or GFF3 @@ -441,7 +445,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI float score = Float.NaN; try { - score = new Float(gffColumns[6]).floatValue(); + score = Float.valueOf(gffColumns[6]).floatValue(); } catch (NumberFormatException ex) { sf = new SequenceFeature(ft, desc, startPos, endPos, featureGroup); @@ -563,32 +567,30 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI } /** - * Returns contents of a Jalview format features file, for visible features, - * as filtered by type and group. Features with a null group are displayed if - * their feature type is visible. Non-positional features may optionally be - * included (with no check on type or group). + * Returns contents of a Jalview format features file, for visible features, as + * filtered by type and group. Features with a null group are displayed if their + * feature type is visible. Non-positional features may optionally be included + * (with no check on type or group). * * @param sequences * @param fr * @param includeNonPositional - * if true, include non-positional features (regardless of group or - * type) + * if true, include non-positional features + * (regardless of group or type) + * @param includeComplement + * if true, include visible complementary + * (CDS/protein) positional features, with + * locations converted to local sequence + * coordinates * @return */ public String printJalviewFormat(SequenceI[] sequences, - FeatureRenderer fr, boolean includeNonPositional) + FeatureRenderer fr, boolean includeNonPositional, + boolean includeComplement) { Map visibleColours = fr .getDisplayedFeatureCols(); Map featureFilters = fr.getFeatureFilters(); - List visibleFeatureGroups = fr.getDisplayedFeatureGroups(); - - if (!includeNonPositional - && (visibleColours == null || visibleColours.isEmpty())) - { - // no point continuing. - return "No Features Visible"; - } /* * write out feature colours (if we know them) @@ -616,42 +618,153 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI outputFeatureFilters(out, visibleColours, featureFilters); /* - * sort groups alphabetically, and ensure that features with a - * null or empty group are output after those in named groups + * output features within groups */ - List sortedGroups = new ArrayList<>(visibleFeatureGroups); - sortedGroups.remove(null); - sortedGroups.remove(""); - Collections.sort(sortedGroups); - sortedGroups.add(null); - sortedGroups.add(""); + int count = outputFeaturesByGroup(out, fr, types, sequences, + includeNonPositional); + + if (includeComplement) + { + count += outputComplementFeatures(out, fr, sequences); + } - boolean foundSome = false; + return count > 0 ? out.toString() : "No Features Visible"; + } + + /** + * Outputs any visible complementary (CDS/peptide) positional features as + * Jalview format, within feature group. The coordinates of the linked features + * are converted to the corresponding positions of the local sequences. + * + * @param out + * @param fr + * @param sequences + * @return + */ + private int outputComplementFeatures(StringBuilder out, + FeatureRenderer fr, SequenceI[] sequences) + { + AlignViewportI comp = fr.getViewport().getCodingComplement(); + FeatureRenderer fr2 = Desktop.getAlignFrameFor(comp) + .getFeatureRenderer(); /* - * first output any non-positional features + * bin features by feature group and sequence */ - if (includeNonPositional) + Map>> map = new TreeMap<>( + String.CASE_INSENSITIVE_ORDER); + int count = 0; + + for (SequenceI seq : sequences) { - for (int i = 0; i < sequences.length; i++) + /* + * find complementary features + */ + List complementary = findComplementaryFeatures(seq, + fr2); + String seqName = seq.getName(); + + for (SequenceFeature sf : complementary) { - String sequenceName = sequences[i].getName(); - for (SequenceFeature feature : sequences[i].getFeatures() - .getNonPositionalFeatures()) + String group = sf.getFeatureGroup(); + if (!map.containsKey(group)) + { + map.put(group, new LinkedHashMap<>()); // preserves sequence order + } + Map> groupFeatures = map.get(group); + if (!groupFeatures.containsKey(seqName)) + { + groupFeatures.put(seqName, new ArrayList<>()); + } + List foundFeatures = groupFeatures.get(seqName); + foundFeatures.add(sf); + count++; + } + } + + /* + * output features by group + */ + for (Entry>> groupFeatures : map.entrySet()) + { + out.append(newline); + String group = groupFeatures.getKey(); + if (!"".equals(group)) + { + out.append(STARTGROUP).append(TAB).append(group).append(newline); + } + Map> seqFeaturesMap = groupFeatures + .getValue(); + for (Entry> seqFeatures : seqFeaturesMap + .entrySet()) + { + String sequenceName = seqFeatures.getKey(); + for (SequenceFeature sf : seqFeatures.getValue()) { - foundSome = true; - out.append(formatJalviewFeature(sequenceName, feature)); + formatJalviewFeature(out, sequenceName, sf); } } + if (!"".equals(group)) + { + out.append(ENDGROUP).append(TAB).append(group).append(newline); + } } + return count; + } + + /** + * Answers a list of mapped features visible in the (CDS/protein) complement, + * with feature positions translated to local sequence coordinates + * + * @param seq + * @param fr2 + * @return + */ + protected List findComplementaryFeatures(SequenceI seq, + FeatureRenderer fr2) + { /* - * positional features within groups + * avoid duplication of features (e.g. peptide feature + * at all 3 mapped codon positions) */ - foundSome |= outputFeaturesByGroup(out, fr, sortedGroups, types, - sequences); + List found = new ArrayList<>(); + List complementary = new ArrayList<>(); + + for (int pos = seq.getStart(); pos <= seq.getEnd(); pos++) + { + MappedFeatures mf = fr2.findComplementFeaturesAtResidue(seq, pos); + + if (mf != null) + { + MapList mapping = mf.mapping.getMap(); + for (SequenceFeature sf : mf.features) + { + /* + * make a virtual feature with local coordinates + */ + if (!found.contains(sf)) + { + String group = sf.getFeatureGroup(); + if (group == null) + { + group = ""; + } + found.add(sf); + int begin = sf.getBegin(); + int end = sf.getEnd(); + int[] range = mf.mapping.getTo() == seq.getDatasetSequence() + ? mapping.locateInTo(begin, end) + : mapping.locateInFrom(begin, end); + SequenceFeature sf2 = new SequenceFeature(sf, range[0], + range[1], group, sf.getScore()); + complementary.add(sf2); + } + } + } + } - return foundSome ? out.toString() : "No Features Visible"; + return complementary; } /** @@ -697,72 +810,109 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI /** * Appends output of visible sequence features within feature groups to the * output buffer. Groups other than the null or empty group are sandwiched by - * STARTGROUP and ENDGROUP lines. Answers true if at least one feature was - * written, else false. + * STARTGROUP and ENDGROUP lines. Answers the number of features written. * * @param out * @param fr - * @param groups * @param featureTypes * @param sequences + * @param includeNonPositional * @return */ - private boolean outputFeaturesByGroup(StringBuilder out, - FeatureRenderer fr, List groups, String[] featureTypes, - SequenceI[] sequences) + private int outputFeaturesByGroup(StringBuilder out, + FeatureRenderer fr, String[] featureTypes, + SequenceI[] sequences, boolean includeNonPositional) { - boolean foundSome = false; - for (String group : groups) + List featureGroups = fr.getFeatureGroups(); + + /* + * sort groups alphabetically, and ensure that features with a + * null or empty group are output after those in named groups + */ + List sortedGroups = new ArrayList<>(featureGroups); + sortedGroups.remove(null); + sortedGroups.remove(""); + Collections.sort(sortedGroups); + sortedGroups.add(null); + sortedGroups.add(""); + + int count = 0; + List visibleGroups = fr.getDisplayedFeatureGroups(); + + /* + * loop over all groups (may be visible or not); + * non-positional features are output even if group is not visible + */ + for (String group : sortedGroups) { boolean firstInGroup = true; - boolean isNamedGroup = (group != null && !"".equals(group)); + boolean isNullGroup = group == null || "".equals(group); - /* - * output positional features within groups - */ for (int i = 0; i < sequences.length; i++) { String sequenceName = sequences[i].getName(); List features = new ArrayList<>(); - if (featureTypes.length > 0) + + /* + * get any non-positional features in this group, if wanted + * (for any feature type, whether visible or not) + */ + if (includeNonPositional) + { + features.addAll(sequences[i].getFeatures() + .getFeaturesForGroup(false, group)); + } + + /* + * add positional features for visible feature types, but + * (for named groups) only if feature group is visible + */ + if (featureTypes.length > 0 + && (isNullGroup || visibleGroups.contains(group))) { features.addAll(sequences[i].getFeatures().getFeaturesForGroup( true, group, featureTypes)); } - for (SequenceFeature sequenceFeature : features) + for (SequenceFeature sf : features) { - if (fr.isVisible(sequenceFeature)) + if (sf.isNonPositional() || fr.isVisible(sf)) { - foundSome = true; - if (firstInGroup && isNamedGroup) + count++; + if (firstInGroup) { - out.append(newline).append(STARTGROUP).append(TAB) - .append(group).append(newline); + out.append(newline); + if (!isNullGroup) + { + out.append(STARTGROUP).append(TAB).append(group) + .append(newline); + } } firstInGroup = false; - out.append(formatJalviewFeature(sequenceName, sequenceFeature)); + formatJalviewFeature(out, sequenceName, sf); } } } - if (isNamedGroup && !firstInGroup) + if (!isNullGroup && !firstInGroup) { out.append(ENDGROUP).append(TAB).append(group).append(newline); } } - return foundSome; + return count; } /** + * Formats one feature in Jalview format and appends to the string buffer + * * @param out * @param sequenceName * @param sequenceFeature */ - protected String formatJalviewFeature( - String sequenceName, SequenceFeature sequenceFeature) + protected void formatJalviewFeature( + StringBuilder out, String sequenceName, + SequenceFeature sequenceFeature) { - StringBuilder out = new StringBuilder(64); if (sequenceFeature.description == null || sequenceFeature.description.equals("")) { @@ -787,7 +937,8 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI if (sequenceFeature.description.indexOf(href) == -1) { - out.append(" " + label + ""); + out.append(" ") + .append(label).append(""); } } @@ -812,8 +963,6 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI out.append(sequenceFeature.score); } out.append(newline); - - return out.toString(); } /** @@ -872,34 +1021,40 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI * Returns features output in GFF2 format * * @param sequences - * the sequences whose features are to be output + * the sequences whose features are to be + * output * @param visible - * a map whose keys are the type names of visible features + * a map whose keys are the type names of + * visible features * @param visibleFeatureGroups * @param includeNonPositionalFeatures + * @param includeComplement * @return */ public String printGffFormat(SequenceI[] sequences, - FeatureRenderer fr, boolean includeNonPositionalFeatures) + FeatureRenderer fr, boolean includeNonPositionalFeatures, + boolean includeComplement) { + FeatureRenderer fr2 = null; + if (includeComplement) + { + AlignViewportI comp = fr.getViewport().getCodingComplement(); + fr2 = Desktop.getAlignFrameFor(comp).getFeatureRenderer(); + } + Map visibleColours = fr.getDisplayedFeatureCols(); StringBuilder out = new StringBuilder(256); out.append(String.format("%s %d\n", GFF_VERSION, gffVersion == 0 ? 2 : gffVersion)); - if (!includeNonPositionalFeatures - && (visibleColours == null || visibleColours.isEmpty())) - { - return out.toString(); - } - String[] types = visibleColours == null ? new String[0] : visibleColours.keySet() .toArray(new String[visibleColours.keySet().size()]); for (SequenceI seq : sequences) { + List seqFeatures = new ArrayList<>(); List features = new ArrayList<>(); if (includeNonPositionalFeatures) { @@ -909,51 +1064,29 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI { features.addAll(seq.getFeatures().getPositionalFeatures(types)); } - for (SequenceFeature sf : features) { - if (!sf.isNonPositional() && !fr.isVisible(sf)) + if (sf.isNonPositional() || fr.isVisible(sf)) { /* - * feature hidden by group visibility, colour threshold, + * drop features hidden by group visibility, colour threshold, * or feature filter condition */ - continue; - } - - String source = sf.featureGroup; - if (source == null) - { - source = sf.getDescription(); + seqFeatures.add(sf); } + } - out.append(seq.getName()); - out.append(TAB); - out.append(source); - out.append(TAB); - out.append(sf.type); - out.append(TAB); - out.append(sf.begin); - out.append(TAB); - out.append(sf.end); - out.append(TAB); - out.append(sf.score); - out.append(TAB); - - int strand = sf.getStrand(); - out.append(strand == 1 ? "+" : (strand == -1 ? "-" : ".")); - out.append(TAB); - - String phase = sf.getPhase(); - out.append(phase == null ? "." : phase); - - // miscellaneous key-values (GFF column 9) - String attributes = sf.getAttributes(); - if (attributes != null) - { - out.append(TAB).append(attributes); - } + if (includeComplement) + { + seqFeatures.addAll(findComplementaryFeatures(seq, fr2)); + } + /* + * sort features here if wanted + */ + for (SequenceFeature sf : seqFeatures) + { + formatGffFeature(out, seq, sf); out.append(newline); } } @@ -962,6 +1095,46 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI } /** + * Formats one feature as GFF and appends to the string buffer + */ + private void formatGffFeature(StringBuilder out, SequenceI seq, + SequenceFeature sf) + { + String source = sf.featureGroup; + if (source == null) + { + source = sf.getDescription(); + } + + out.append(seq.getName()); + out.append(TAB); + out.append(source); + out.append(TAB); + out.append(sf.type); + out.append(TAB); + out.append(sf.begin); + out.append(TAB); + out.append(sf.end); + out.append(TAB); + out.append(sf.score); + out.append(TAB); + + int strand = sf.getStrand(); + out.append(strand == 1 ? "+" : (strand == -1 ? "-" : ".")); + out.append(TAB); + + String phase = sf.getPhase(); + out.append(phase == null ? "." : phase); + + // miscellaneous key-values (GFF column 9) + String attributes = sf.getAttributes(); + if (attributes != null) + { + out.append(TAB).append(attributes); + } + } + + /** * Returns a mapping given list of one or more Align descriptors (exonerate * format) *