X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fio%2FFeaturesFile.java;h=cfc08fbdcaebb2e8917e9fe0e0c8d0c27e342806;hb=153dd62dc91da13ae732600e6ea55ddbe15eab39;hp=462ee22dd65d8260f500437db9f2b22d4abc7bfe;hpb=506d60f0e188723ddc91c26824b41ac7034df3fe;p=jalview.git diff --git a/src/jalview/io/FeaturesFile.java b/src/jalview/io/FeaturesFile.java index 462ee22..cfc08fb 100755 --- a/src/jalview/io/FeaturesFile.java +++ b/src/jalview/io/FeaturesFile.java @@ -1,20 +1,19 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4) - * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle + * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6) + * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. + * This file is part of Jalview. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Jalview. If not, see . */ package jalview.io; @@ -23,6 +22,7 @@ import java.util.*; import jalview.datamodel.*; import jalview.schemes.*; +import jalview.util.Format; /** * Parse and create Jalview Features files Detects GFF format features files and @@ -36,6 +36,12 @@ import jalview.schemes.*; public class FeaturesFile extends AlignFile { /** + * work around for GFF interpretation bug where source string becomes + * description rather than a group + */ + private boolean doGffSource = true; + + /** * Creates a new FeaturesFile object. */ public FeaturesFile() @@ -46,12 +52,12 @@ public class FeaturesFile extends AlignFile * Creates a new FeaturesFile object. * * @param inFile - * DOCUMENT ME! + * DOCUMENT ME! * @param type - * DOCUMENT ME! + * DOCUMENT ME! * * @throws IOException - * DOCUMENT ME! + * DOCUMENT ME! */ public FeaturesFile(String inFile, String type) throws IOException { @@ -68,7 +74,7 @@ public class FeaturesFile extends AlignFile * replace links with %LINK% Both need to read links in HTML however * * @throws IOException - * DOCUMENT ME! + * DOCUMENT ME! */ public boolean parse(AlignmentI align, Hashtable colours, boolean removeHTML) @@ -81,7 +87,7 @@ public class FeaturesFile extends AlignFile * replace links with %LINK% Both need to read links in HTML however * * @throws IOException - * DOCUMENT ME! + * DOCUMENT ME! */ public boolean parse(AlignmentI align, Hashtable colours, Hashtable featureLink, boolean removeHTML) @@ -98,9 +104,10 @@ public class FeaturesFile extends AlignFile SequenceFeature sf; String featureGroup = null, groupLink = null; Hashtable typeLink = new Hashtable(); - + /** + * when true, assume GFF style features rather than Jalview style. + */ boolean GFFFile = true; - while ((line = nextLine()) != null) { if (line.startsWith("#")) @@ -109,6 +116,15 @@ public class FeaturesFile extends AlignFile } st = new StringTokenizer(line, "\t"); + if (st.countTokens() == 1) + { + if (line.trim().equalsIgnoreCase("GFF")) + { + // Start parsing file as if it might be GFF again. + GFFFile = true; + continue; + } + } if (st.countTokens() > 1 && st.countTokens() < 4) { GFFFile = false; @@ -132,8 +148,197 @@ public class FeaturesFile extends AlignFile } else { - UserColourScheme ucs = new UserColourScheme(st.nextToken()); - colours.put(type, ucs.findColour('A')); + Object colour = null; + String colscheme = st.nextToken(); + if (colscheme.indexOf("|") > -1 + || colscheme.trim().equalsIgnoreCase("label")) + { + // Parse '|' separated graduated colourscheme fields: + // [label|][mincolour|maxcolour|[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] + // can either provide 'label' only, first is optional, next two + // colors are required (but may be + // left blank), next is optional, nxt two min/max are required. + // first is either 'label' + // first/second and third are both hexadecimal or word equivalent + // colour. + // next two are values parsed as floats. + // fifth is either 'above','below', or 'none'. + // sixth is a float value and only required when fifth is either + // 'above' or 'below'. + StringTokenizer gcol = new StringTokenizer(colscheme, "|", true); + // set defaults + int threshtype = AnnotationColourGradient.NO_THRESHOLD; + float min = Float.MIN_VALUE, max = Float.MAX_VALUE, threshval = Float.NaN; + boolean labelCol = false; + // Parse spec line + String mincol = gcol.nextToken(); + if (mincol=="|") + { + System.err + .println("Expected either 'label' or a colour specification in the line: "+line ); + continue; + } + String maxcol = null; + if (mincol.toLowerCase().indexOf("label") == 0) + { + labelCol = true; + mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); // skip '|' + mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); + } + String abso = null, minval, maxval; + if (mincol != null) + { + // at least four more tokens + if (mincol.equals("|")) + { + mincol=""; + } else { + gcol.nextToken(); // skip next '|' + } + // continue parsing rest of line + maxcol = gcol.nextToken(); + if (maxcol.equals("|")) + { + maxcol=""; + } else { + gcol.nextToken(); // skip next '|' + } + abso = gcol.nextToken(); + gcol.nextToken(); // skip next '|' + if (abso.toLowerCase().indexOf("abso") != 0) + { + minval = abso; + abso = null; + } + else + { + minval = gcol.nextToken(); + gcol.nextToken(); // skip next '|' + } + maxval = gcol.nextToken(); + if (gcol.hasMoreTokens()) { + gcol.nextToken(); // skip next '|' + } + try + { + if (minval.length() > 0) + { + min = new Float(minval).floatValue(); + } + } catch (Exception e) + { + System.err + .println("Couldn't parse the minimum value for graduated colour for type (" + + colscheme + + ") - did you misspell 'auto' for the optional automatic colour switch ?"); + e.printStackTrace(); + } + try + { + if (maxval.length() > 0) + { + max = new Float(maxval).floatValue(); + } + } catch (Exception e) + { + System.err + .println("Couldn't parse the maximum value for graduated colour for type (" + + colscheme + ")"); + e.printStackTrace(); + } + } + else + { + // add in some dummy min/max colours for the label-only + // colourscheme. + mincol = "FFFFFF"; + maxcol = "000000"; + } + try + { + colour = new jalview.schemes.GraduatedColor( + new UserColourScheme(mincol).findColour('A'), + new UserColourScheme(maxcol).findColour('A'), min, + max); + } catch (Exception e) + { + System.err + .println("Couldn't parse the graduated colour scheme (" + + colscheme + ")"); + e.printStackTrace(); + } + if (colour != null) + { + ((jalview.schemes.GraduatedColor) colour) + .setColourByLabel(labelCol); + ((jalview.schemes.GraduatedColor) colour) + .setAutoScaled(abso == null); + // add in any additional parameters + String ttype = null, tval = null; + if (gcol.hasMoreTokens()) + { + // threshold type and possibly a threshold value + ttype = gcol.nextToken(); + if (ttype.toLowerCase().startsWith("below")) + { + ((jalview.schemes.GraduatedColor) colour) + .setThreshType(AnnotationColourGradient.BELOW_THRESHOLD); + } + else if (ttype.toLowerCase().startsWith("above")) + { + ((jalview.schemes.GraduatedColor) colour) + .setThreshType(AnnotationColourGradient.ABOVE_THRESHOLD); + } + else + { + ((jalview.schemes.GraduatedColor) colour) + .setThreshType(AnnotationColourGradient.NO_THRESHOLD); + if (!ttype.toLowerCase().startsWith("no")) + { + System.err + .println("Ignoring unrecognised threshold type : " + + ttype); + } + } + } + if (((GraduatedColor) colour).getThreshType() != AnnotationColourGradient.NO_THRESHOLD) + { + try + { + gcol.nextToken(); + tval = gcol.nextToken(); + ((jalview.schemes.GraduatedColor) colour) + .setThresh(new Float(tval).floatValue()); + } catch (Exception e) + { + System.err + .println("Couldn't parse threshold value as a float: (" + + tval + ")"); + e.printStackTrace(); + } + } + // parse the thresh-is-min token ? + if (gcol.hasMoreTokens()) + { + System.err + .println("Ignoring additional tokens in parameters in graduated colour specification\n"); + while (gcol.hasMoreTokens()) + { + System.err.println("|" + gcol.nextToken()); + } + System.err.println("\n"); + } + } + } + else + { + UserColourScheme ucs = new UserColourScheme(colscheme); + colour = ucs.findColour('A'); + } + if (colour != null) + { + colours.put(type, colour); + } if (st.hasMoreElements()) { String link = st.nextToken(); @@ -144,7 +349,6 @@ public class FeaturesFile extends AlignFile } featureLink.put(type, link); } - } continue; } @@ -161,20 +365,49 @@ public class FeaturesFile extends AlignFile if (seq != null) { desc = st.nextToken(); + String group = null; + if (doGffSource && desc.indexOf(' ') == -1) + { + // could also be a source term rather than description line + group = new String(desc); + } type = st.nextToken(); try { - start = Integer.parseInt(st.nextToken()); + String stt = st.nextToken(); + if (stt.length() == 0 || stt.equals("-")) + { + start = 0; + } + else + { + start = Integer.parseInt(stt); + } } catch (NumberFormatException ex) { start = 0; } try { - end = Integer.parseInt(st.nextToken()); + String stt = st.nextToken(); + if (stt.length() == 0 || stt.equals("-")) + { + end = 0; + } + else + { + end = Integer.parseInt(stt); + } } catch (NumberFormatException ex) { - end = -1; + end = 0; + } + // TODO: decide if non positional feature assertion for input data + // where end==0 is generally valid + if (end == 0) + { + // treat as non-positional feature, regardless. + start = 0; } try { @@ -184,7 +417,7 @@ public class FeaturesFile extends AlignFile score = 0; } - sf = new SequenceFeature(type, desc, start, end, score, null); + sf = new SequenceFeature(type, desc, start, end, score, group); try { @@ -201,6 +434,9 @@ public class FeaturesFile extends AlignFile { attributes.append("\t" + st.nextElement()); } + // TODO validate and split GFF2 attributes field ? parse out + // ([A-Za-z][A-Za-z0-9_]*) ; and add as + // sf.setValue(attrib, val); sf.setValue("ATTRIBUTES", attributes.toString()); } @@ -267,9 +503,19 @@ public class FeaturesFile extends AlignFile UserColourScheme ucs = new UserColourScheme(type); colours.put(type, ucs.findColour('A')); } - sf = new SequenceFeature(type, desc, "", start, end, featureGroup); - + if (st.hasMoreTokens()) + { + try + { + score = new Float(st.nextToken()).floatValue(); + // update colourgradient bounds if allowed to + } catch (NumberFormatException ex) + { + score = 0; + } + sf.setScore(score); + } if (groupLink != null && removeHTML) { sf.addLink(groupLink); @@ -381,60 +627,104 @@ public class FeaturesFile extends AlignFile } /** - * generate a features file for seqs + * generate a features file for seqs includes non-pos features by default. * * @param seqs - * source of sequence features + * source of sequence features * @param visible - * hash of feature types and colours + * hash of feature types and colours * @return features file contents */ public String printJalviewFormat(SequenceI[] seqs, Hashtable visible) { - return printJalviewFormat(seqs, visible, true); + return printJalviewFormat(seqs, visible, true, true); } /** * generate a features file for seqs with colours from visible (if any) * * @param seqs - * source of features + * source of features * @param visible - * hash of Colours for each feature type + * hash of Colours for each feature type * @param visOnly - * when true only feature types in 'visible' will be output + * when true only feature types in 'visible' will be output + * @param nonpos + * indicates if non-positional features should be output (regardless + * of group or type) * @return features file contents */ public String printJalviewFormat(SequenceI[] seqs, Hashtable visible, - boolean visOnly) + boolean visOnly, boolean nonpos) { StringBuffer out = new StringBuffer(); SequenceFeature[] next; - - if (visOnly && (visible == null || visible.size() < 1)) + boolean featuresGen = false; + if (visOnly && !nonpos && (visible == null || visible.size() < 1)) { + // no point continuing. return "No Features Visible"; } + if (visible != null && visOnly) { // write feature colours only if we're given them and we are generating // viewed features + // TODO: decide if feature links should also be written here ? Enumeration en = visible.keys(); - String type; - int color; + String type, color; while (en.hasMoreElements()) { type = en.nextElement().toString(); - color = Integer.parseInt(visible.get(type).toString()); - out.append(type - + "\t" - + jalview.util.Format - .getHexString(new java.awt.Color(color)) + "\n"); + + if (visible.get(type) instanceof GraduatedColor) + { + GraduatedColor gc = (GraduatedColor) visible.get(type); + color = (gc.isColourByLabel() ? "label|" : "") + + Format.getHexString(gc.getMinColor()) + "|" + + Format.getHexString(gc.getMaxColor()) + + (gc.isAutoScale() ? "|" : "|abso|") + gc.getMin() + "|" + + gc.getMax() + "|"; + if (gc.getThreshType() != AnnotationColourGradient.NO_THRESHOLD) + { + if (gc.getThreshType() == AnnotationColourGradient.BELOW_THRESHOLD) + { + color += "below"; + } + else + { + if (gc.getThreshType() != AnnotationColourGradient.ABOVE_THRESHOLD) + { + System.err.println("WARNING: Unsupported threshold type (" + + gc.getThreshType() + ") : Assuming 'above'"); + } + color += "above"; + } + // add the value + color += "|" + gc.getThresh(); + } + else + { + color += "none"; + } + } + else if (visible.get(type) instanceof java.awt.Color) + { + color = Format.getHexString((java.awt.Color) visible.get(type)); + } + else + { + // legacy support for integer objects containing colour triplet values + color = Format.getHexString(new java.awt.Color(Integer + .parseInt(visible.get(type).toString()))); + } + out.append(type + "\t" + color + "\n"); } } // Work out which groups are both present and visible Vector groups = new Vector(); int groupIndex = 0; + boolean isnonpos = false; for (int i = 0; i < seqs.length; i++) { @@ -443,7 +733,10 @@ public class FeaturesFile extends AlignFile { for (int j = 0; j < next.length; j++) { - if (visOnly && !visible.containsKey(next[j].type)) + isnonpos = next[j].begin == 0 && next[j].end == 0; + if ((!nonpos && isnonpos) + || (!isnonpos && visOnly && !visible + .containsKey(next[j].type))) { continue; } @@ -458,7 +751,6 @@ public class FeaturesFile extends AlignFile } String group = null; - do { @@ -479,8 +771,13 @@ public class FeaturesFile extends AlignFile { for (int j = 0; j < next.length; j++) { - if (visOnly && !visible.containsKey(next[j].type)) + isnonpos = next[j].begin == 0 && next[j].end == 0; + if ((!nonpos && isnonpos) + || (!isnonpos && visOnly && !visible + .containsKey(next[j].type))) { + // skip if feature is nonpos and we ignore them or if we only + // output visible and it isn't non-pos and it's not visible continue; } @@ -495,7 +792,8 @@ public class FeaturesFile extends AlignFile { continue; } - + // we have features to output + featuresGen = true; if (next[j].description == null || next[j].description.equals("")) { @@ -520,9 +818,7 @@ public class FeaturesFile extends AlignFile if (next[j].description.indexOf(href) == -1) { - out - .append("" + label - + ""); + out.append("" + label + ""); } } @@ -534,9 +830,15 @@ public class FeaturesFile extends AlignFile out.append("\t"); } - - out.append(seqs[i].getName() + "\t-1\t" + next[j].begin + "\t" - + next[j].end + "\t" + next[j].type + "\n"); + out.append(seqs[i].getName() + + "\t-1\t" + + next[j].begin + + "\t" + + next[j].end + + "\t" + + next[j].type + + ((next[j].score != Float.NaN) ? "\t" + next[j].score + + "\n" : "\n")); } } } @@ -553,21 +855,34 @@ public class FeaturesFile extends AlignFile } while (groupIndex < groups.size() + 1); + if (!featuresGen) + { + return "No Features Visible"; + } + return out.toString(); } + /** + * generate a gff file for sequence features includes non-pos features by + * default. + * + * @param seqs + * @param visible + * @return + */ public String printGFFFormat(SequenceI[] seqs, Hashtable visible) { - return printGFFFormat(seqs, visible, true); + return printGFFFormat(seqs, visible, true, true); } public String printGFFFormat(SequenceI[] seqs, Hashtable visible, - boolean visOnly) + boolean visOnly, boolean nonpos) { StringBuffer out = new StringBuffer(); SequenceFeature[] next; String source; - + boolean isnonpos; for (int i = 0; i < seqs.length; i++) { if (seqs[i].getSequenceFeatures() != null) @@ -575,7 +890,10 @@ public class FeaturesFile extends AlignFile next = seqs[i].getSequenceFeatures(); for (int j = 0; j < next.length; j++) { - if (visOnly && !visible.containsKey(next[j].type)) + isnonpos = next[j].begin == 0 && next[j].end == 0; + if ((!nonpos && isnonpos) + || (!isnonpos && visOnly && !visible + .containsKey(next[j].type))) { continue; }