JAL-2136 New Phyre2 branch + attempt to resynced with develop features/JAL-2136_phyre2_integration_updated
authortcofoegbu <tcnofoegbu@dundee.ac.uk>
Wed, 14 Jun 2017 12:25:28 +0000 (13:25 +0100)
committertcofoegbu <tcnofoegbu@dundee.ac.uk>
Wed, 14 Jun 2017 12:25:28 +0000 (13:25 +0100)
24 files changed:
1  2 
resources/lang/Messages.properties
src/MCview/AppletPDBCanvas.java
src/MCview/PDBCanvas.java
src/jalview/api/SiftsClientI.java
src/jalview/appletgui/AlignFrame.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/jmol/JmolCommands.java
src/jalview/fts/service/pdb/PDBFTSPanel.java
src/jalview/fts/service/uniprot/UniprotFTSPanel.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/StructureViewerBase.java
src/jalview/io/AnnotationFile.java
src/jalview/io/StructureFile.java
src/jalview/jbgui/GStructureChooser.java
src/jalview/structure/StructureSelectionManager.java
src/jalview/structures/models/AAStructureBindingModel.java
src/jalview/ws/phyre2/Phyre2Client.java
src/jalview/ws/sifts/SiftsClient.java
test/jalview/ext/jmol/JmolViewerTest.java
test/jalview/io/AnnotationFileIOTest.java
test/jalview/structures/models/AAStructureBindingModelTest.java
test/jalview/ws/sifts/SiftsClientTest.java

@@@ -1300,16 -1299,7 +1300,19 @@@ label.edit_sequence_url_link = Edit seq
  warn.name_cannot_be_duplicate = User-defined URL names must be unique and cannot be MIRIAM ids
  label.invalid_name = Invalid Name !
  label.output_seq_details = Output Sequence Details to list all database references
+ label.phyre2_model_prediction = 3D Protein Model prediction with Phyre2
+ label.run_phyre2_prediction = Run Phyre2 Prediction
+ status.obtaining_mapping_with_phyre2_template_alignment = Obtaining mapping with Phyre2 Template alignment 
  label.urllinks = Links
 +label.default_cache_size = Default Cache Size
 +action.clear_cached_items = Clear Cached Items
 +label.togglehidden = Show hidden regions
 +label.quality_descr = Alignment Quality based on Blosum62 scores
 +label.conservation_descr = Conservation of total alignment less than {0}% gaps
 +label.consensus_descr = PID
 +label.complement_consensus_descr = PID for cDNA
 +label.strucconsensus_descr = PID for base pairs
 +label.occupancy_descr = Number of aligned positions 
 +label.show_experimental = Enable experimental features
 +label.show_experimental_tip = Enable any new and currently 'experimental' features (see Latest Release Notes for details)
 +label.warning_hidden = Warning: {0} {1} is currently hidden
Simple merge
Simple merge
@@@ -21,8 -21,7 +21,8 @@@
  package jalview.api;
  
  import jalview.datamodel.SequenceI;
 -import jalview.structure.StructureMappingClient.StructureMappingException;
 +import jalview.structure.StructureMapping;
- import jalview.ws.sifts.MappingOutputPojo;
++import jalview.structures.models.MappingOutputModel;
  import jalview.ws.sifts.SiftsException;
  import jalview.xml.binding.sifts.Entry.Entity;
  
@@@ -93,29 -92,7 +93,29 @@@ public interface SiftsClient
     */
    public boolean isAccessionMatched(String accessionId);
  
 +  /**
 +   * 
 +   * @param mop
 +   *          MappingOutputPojo
 +   * @return Sequence<->Structure mapping as int[][]
 +   * @throws SiftsException
 +   */
-   public StringBuilder getMappingOutput(MappingOutputPojo mop)
++  public StringBuilder getMappingOutput(MappingOutputModel mop)
 +          throws SiftsException;
  
 +  /**
 +   * 
 +   * @param seq
 +   *          sequence to generate mapping against the structure
 +   * @param pdbFile
 +   *          PDB file for the mapping
 +   * @param chain
 +   *          the chain of the entry to use for mapping
 +   * @return StructureMapping
 +   * @throws SiftsException
 +   */
 +  public StructureMapping getSiftsStructureMapping(SequenceI seq,
 +          String pdbFile, String chain) throws SiftsException;
  
    /**
     * Get residue by residue mapping for a given Sequence and SIFTs entity
Simple merge
@@@ -24,9 -24,10 +24,10 @@@ import jalview.api.AlignmentViewPanel
  import jalview.api.FeatureRenderer;
  import jalview.api.SequenceRenderer;
  import jalview.datamodel.AlignmentI;
 -import jalview.datamodel.ColumnSelection;
 +import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
+ import jalview.gui.IProgressIndicator;
  import jalview.io.DataSourceType;
  import jalview.io.StructureFile;
  import jalview.schemes.ColourSchemeI;
Simple merge
Simple merge
Simple merge
@@@ -26,9 -26,12 +26,13 @@@ import jalview.datamodel.AlignmentAnnot
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.Annotation;
  import jalview.datamodel.ColumnSelection;
+ import jalview.datamodel.DynamicData;
+ import jalview.datamodel.DynamicData.DataType;
  import jalview.datamodel.GraphLine;
 +import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.HiddenSequences;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.PDBEntry.Type;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
  import jalview.schemes.ColourSchemeI;
@@@ -692,9 -708,10 +716,10 @@@ public class AnnotationFil
      return readAnnotationFile(al, null, file, sourceType);
    }
  
 -  public boolean readAnnotationFile(AlignmentI al, ColumnSelection colSel,
 +  public boolean readAnnotationFile(AlignmentI al, HiddenColumns hidden,
            String file, DataSourceType sourceType)
    {
+     baseUri = "";
      BufferedReader in = null;
      try
      {
      return false;
    }
  
-   long nlinesread = 0;
-   String lastread = "";
-   private static String GRAPHLINE = "GRAPHLINE", COMBINE = "COMBINE";
  
 -  public boolean parseAnnotationFrom(AlignmentI al, ColumnSelection colSel,
 +  public boolean parseAnnotationFrom(AlignmentI al, HiddenColumns hidden,
            BufferedReader in) throws Exception
    {
      nlinesread = 0;
      return modified;
    }
  
++
+   /**
+    * Resolve structural model to a reference sequence and register it to
+    * StructureSelectionManager
+    * 
+    * @param al
+    * @param querySequence
+    * @param templateSeq
+    * @param structModelHeader
+    * @param structModelData
+    * @return true if model and sequence was added
+    */
+   static boolean processStructModel(AlignmentI al, SequenceI querySequence,
+           SequenceI templateSeq,
+  String[] structModelHeader,
+           String[] structModelData, String baseUri)
+   {
+     String warningMessage = null;
+     boolean added = false;
+     try {
+       String structureModelFile = resolveAbsolutePath(structModelData[2],
+               baseUri);
+       String fastaMappingFile = resolveAbsolutePath(structModelData[3],
+               baseUri);
+       // System.out.println("Model File >> " + structureModelFile);
+       // System.out.println("Fasta File >> " + fastaMappingFile);
+       String modelName = StructureFile.safeName(structureModelFile);
+       PDBEntry phyre2PDBEntry = new PDBEntry(modelName, " ",
+               Type.PDB,
+               structureModelFile);
+       List<DynamicData> phyreDD = generatePhyreDynamicDataList(
+               structModelHeader, structModelData);
+       phyre2PDBEntry.setProperty("DYNAMIC_DATA_PHYRE2", phyreDD);
+       templateSeq.getDatasetSequence().addPDBId(phyre2PDBEntry);
+       if (querySequence != null)
+       {
+         querySequence.getDatasetSequence().addPDBId(phyre2PDBEntry);
+       }
+       StructureSelectionManager ssm = StructureSelectionManager
+               .getStructureSelectionManager();
+       ssm.registerPhyre2Template(structureModelFile, fastaMappingFile);
+       added = true;
+     } catch (Exception x)
+     {
+       warningMessage = x.toString();
+     } finally {
+       if (warningMessage !=null)
+       {
+         System.err.println("Warnings whilst processing STRUCTMODEL: "+warningMessage);
+       }
+     }
+     return added;
+   }
+   static List<DynamicData> generatePhyreDynamicDataList(
+           String[] headerArray,
+           String[] dataArray)
+   {
+     if (headerArray == null || dataArray == null)
+     {
+       throw new IllegalArgumentException(
+               "Header or data arrays must not be null");
+     }
+     if (headerArray.length != dataArray.length)
+     {
+       throw new IllegalArgumentException(
+               "Header and data arrays must be of same lenght");
+     }
+     List<DynamicData> dynamicDataList = new ArrayList<DynamicData>();
+     int x = 0;
+     for (String data : dataArray)
+     {
+       // first four column should be hidden;
+       boolean show = (x > 4);
+       dynamicDataList.add(new DynamicData(headerArray[x], data, DataType.S,
+               "PHYRE2", show));
+       x++;
+     }
+     return dynamicDataList;
+   }
+   static String resolveAbsolutePath(String relURI, String _baseUri)
+   {
+     if (relURI.indexOf(":/") > -1 || relURI.startsWith("/")
+             || "".equals(_baseUri) || relURI.startsWith(_baseUri))
+     {
+       return relURI;
+     }
+     return _baseUri + relURI;
+   }
 -  private void parseHideCols(ColumnSelection colSel, String nextToken)
 +  private void parseHideCols(HiddenColumns hidden, String nextToken)
    {
      StringTokenizer inval = new StringTokenizer(nextToken, ",");
      while (inval.hasMoreTokens())
@@@ -406,10 -406,9 +406,10 @@@ public abstract class StructureFile ext
     * make a friendly ID string.
     * 
     * @param dataName
 -   * @return truncated dataName to after last '/'
 +   * @return truncated dataName to after last '/' and pruned .extension if
 +   *         present
     */
-   protected String safeName(String dataName)
+   public static String safeName(String dataName)
    {
      int p = 0;
      while ((p = dataName.indexOf("/")) > -1 && p < dataName.length())
@@@ -662,19 -701,6 +701,21 @@@ public abstract class GStructureChoose
  
      private boolean addSeparatorAfter;
  
++
 +    /**
 +     * Model for structure filter option
 +     * 
 +     * @param name
 +     *          - the name of the Option
 +     * @param value
 +     *          - the value of the option
 +     * @param view
 +     *          - the category of the filter option
 +     * @param addSeparatorAfter
 +     *          - if true, a horizontal separator is rendered immediately after
 +     *          this filter option, otherwise
 +     */
++
      public FilterOption(String name, String value, String view,
              boolean addSeparatorAfter)
      {
      return cmb_filterOption;
    }
  
 +  /**
 +   * Custom ListCellRenderer for adding a separator between different categories
 +   * of structure chooser filter option drop-down.
 +   * 
 +   * @author tcnofoegbu
 +   *
 +   */
++
+   public JTable getPhyreResultTable()
+   {
+     return tbl_phyre2_summary;
+   }
++
    public abstract class CustomComboSeparatorsRenderer implements
            ListCellRenderer<Object>
    {
@@@ -386,11 -402,10 +404,12 @@@ public class StructureSelectionManage
      boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
      try
      {
-       sourceType = AppletFormatAdapter.checkProtocol(pdbFile);
-       pdb = new JmolParser(pdbFile, sourceType);
 -      pdb = new JmolParser(pdbFile, protocol);
++      pdb = new JmolParser(pdbFile,
++              AppletFormatAdapter.checkProtocol(pdbFile));
++
  
        if (pdb.getId() != null && pdb.getId().trim().length() > 0
-               && DataSourceType.FILE == sourceType)
+               && DataSourceType.FILE == protocol)
        {
          registerPDBFile(pdb.getId().trim(), pdbFile);
        }
         * Attempt pairwise alignment of the sequence with each chain in the PDB,
         * and remember the highest scoring chain
         */
 -      int max = -10;
 +      float max = -10;
        AlignSeq maxAlignseq = null;
-       String maxChainId = " ";
+       String maxChainId = StructureMapping.NO_CHAIN; // space
        PDBChain maxChain = null;
        boolean first = true;
        for (PDBChain chain : pdb.getChains())
    private StructureMapping getStructureMapping(SequenceI seq,
            String pdbFile, String targetChainId, StructureFile pdb,
            PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
-           AlignSeq maxAlignseq) throws SiftsException
+           AlignSeq maxAlignseq) throws Exception
    {
      StructureMapping curChainMapping = siftsClient
 -            .getStructureMapping(seq, pdbFile, targetChainId);
 +            .getSiftsStructureMapping(seq, pdbFile, targetChainId);
      try
      {
        PDBChain chain = pdb.findChain(targetChainId);
index 0000000,80dc841..71cf920
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,430 +1,434 @@@
+ package jalview.ws.phyre2;
++import jalview.analysis.scoremodels.ScoreMatrix;
++import jalview.analysis.scoremodels.ScoreModels;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.SequenceI;
+ import jalview.fts.core.DecimalFormatTableCellRenderer;
+ import jalview.io.AppletFormatAdapter;
+ import jalview.io.DataSourceType;
+ import jalview.io.FileFormat;
+ import jalview.io.FormatAdapter;
+ import jalview.io.StructureFile;
 -import jalview.schemes.ResidueProperties;
+ import jalview.structure.StructureMapping;
+ import jalview.structure.StructureMappingClient;
+ import jalview.structures.models.MappingOutputModel;
+ import jalview.util.Comparison;
+ import jalview.util.Format;
+ import java.io.BufferedReader;
+ import java.io.FileReader;
+ import java.io.IOException;
+ import java.io.PrintStream;
+ import java.util.ArrayList;
+ import java.util.HashMap;
+ import java.util.List;
+ import javax.swing.JTable;
+ import javax.swing.table.DefaultTableModel;
+ public class Phyre2Client extends StructureMappingClient
+ {
+   private final static String NEWLINE = System.lineSeparator();
+   private String fastaMappingFile;
++  ScoreMatrix pam250 = ScoreModels.getInstance().getPam250();
++
+   public Phyre2Client(StructureFile structureFile)
+   {
+     this.structureFile = structureFile;
+   }
+   public StructureMapping getStructureMapping(SequenceI seq,
+           String pdbFile, String fMappingFile, String chain)
+   {
+     this.fastaMappingFile = fMappingFile;
+     return getStructureMapping(seq, pdbFile, chain);
+   }
+   @Override
+   public StructureMapping getStructureMapping(SequenceI seq,
+           String pdbFile, String chain)
+   {
+     final StringBuilder mappingDetails = new StringBuilder(128);
+     PrintStream ps = new PrintStream(System.out)
+     {
+       @Override
+       public void print(String x)
+       {
+         mappingDetails.append(x);
+       }
+       @Override
+       public void println()
+       {
+         mappingDetails.append(NEWLINE);
+       }
+     };
+     HashMap<Integer, int[]> mapping = getPhyre2FastaMapping(seq, ps);
+     String mappingOutput = mappingDetails.toString();
+     StructureMapping phyre2ModelMapping = new StructureMapping(seq,
+             pdbFile, structureFile.getId(), chain, mapping, mappingOutput);
+     return phyre2ModelMapping;
+   }
+   public HashMap<Integer, int[]> getPhyre2FastaMapping(SequenceI inputSeq,
+           java.io.PrintStream os)
+   {
+     HashMap<Integer, int[]> mapping = new HashMap<Integer, int[]>();
+     AlignmentI seq2Phyre2ModelFastaMapping = null;
+     try
+     {
+       String fastaFile = getFastaMappingFile();
+       DataSourceType protocol = AppletFormatAdapter
+               .checkProtocol(fastaFile);
+       seq2Phyre2ModelFastaMapping = new FormatAdapter().readFile(fastaFile,
+               protocol, FileFormat.Fasta);
+     } catch (IOException e1)
+     {
+       e1.printStackTrace();
+     }
+     SequenceI[] seqs = seq2Phyre2ModelFastaMapping.getSequencesArray();
+     SequenceI tSequenceRes = seqs[0];
+     SequenceI tStructureRes = seqs[1];
+     // Essential to resolve fastaAlignment to input sequence and model sequence
+     // coordinates
+     tSequenceRes.setStart(inputSeq.getStart());
+     tSequenceRes.setEnd(inputSeq.getEnd());
+     tStructureRes.setStart(structureFile.getSeqsAsArray()[0].getStart());
+     tStructureRes.setEnd(structureFile.getSeqsAsArray()[0].getEnd());
+     try
+     {
+       int sequenceResLength = tSequenceRes.getLength();
+       int structureResLength = tStructureRes.getLength();
+       if (sequenceResLength == structureResLength)
+       {
+         int prevStructResNum = -1;
+         int alignmentLength = sequenceResLength + tSequenceRes.getStart();
+         for (int x = 0; x < alignmentLength; x++)
+         {
+           int alignSeqResidueIndex = tSequenceRes.findIndex(x);
+           int structResNum = tStructureRes
+                   .findPosition(alignSeqResidueIndex);
+           int sequenceResNum = tSequenceRes
+                   .findPosition(alignSeqResidueIndex - 1);
+           if (structResNum != prevStructResNum)
+           {
+             // System.out.println(sequenceResNum + " : " + prevStructResNum);
+             mapping.put(sequenceResNum, new int[] { prevStructResNum,
+                 StructureMapping.UNASSIGNED });
+           }
+           prevStructResNum = structResNum;
+         }
+       }
+     } catch (Exception e)
+     {
+       e.printStackTrace();
+     }
+     /*
+      * now populate atom positions for structure residues (and remove
+      * residue if atom position cannot be found)
+      */
+     try
+     {
+       populateAtomPositions(" ", mapping);
+     } catch (IllegalArgumentException e)
+     {
+       e.printStackTrace();
+     } catch (StructureMappingException e)
+     {
+       e.printStackTrace();
+     }
+     if (os != null)
+     {
+       MappingOutputModel mop = new MappingOutputModel();
+       mop.setSeqStart(tSequenceRes.getStart());
+       mop.setSeqEnd(tSequenceRes.getEnd());
+       mop.setSeqName(tSequenceRes.getName());
+       mop.setSeqResidue(tSequenceRes.getSequenceAsString());
+       mop.setStrStart(tStructureRes.getStart());
+       mop.setStrEnd(tStructureRes.getEnd());
+       mop.setStrName(tStructureRes.getName());
+       mop.setStrResidue(tStructureRes.getSequenceAsString());
+       mop.setType("pep");
+       try
+       {
+         os.print(getMappingOutput(mop).toString());
+       } catch (Exception e)
+       {
+         e.printStackTrace();
+       }
+       os.println();
+     }
+     return mapping;
+   }
+   private String getFastaMappingFile()
+   {
+     return fastaMappingFile;
+   }
+   void setFastaMappingFile(String fastaMappingFile)
+   {
+     this.fastaMappingFile = fastaMappingFile;
+   }
+   @Override
+   public StringBuffer getMappingOutput(MappingOutputModel mp)
+           throws StructureMappingException
+   {
+     String seqRes = mp.getSeqResidue();
+     String seqName = mp.getSeqName();
+     int sStart = mp.getSeqStart();
+     int sEnd = mp.getSeqEnd();
+     String strRes = mp.getStrResidue();
+     String strName = mp.getStrName();
+     int pdbStart = mp.getStrStart();
+     int pdbEnd = mp.getStrEnd();
+     String type = mp.getType();
+     int maxid = (seqName.length() >= strName.length()) ? seqName.length()
+             : strName.length();
+     int len = 72 - maxid - 1;
+     int nochunks = ((seqRes.length()) / len)
+             + ((seqRes.length()) % len > 0 ? 1 : 0);
+     // output mappings
+     StringBuffer output = new StringBuffer();
+     output.append(NEWLINE);
+     output.append("Sequence \u27f7 Structure mapping details").append(
+             NEWLINE);
+     output.append("Method: Phyre2 Alignment");
+     output.append(NEWLINE).append(NEWLINE);
+     output.append(new Format("%" + maxid + "s").form(seqName));
+     output.append(" :  ");
+     output.append(String.valueOf(sStart));
+     output.append(" - ");
+     output.append(String.valueOf(sEnd));
+     output.append(" Maps to ");
+     output.append(NEWLINE);
+     output.append(new Format("%" + maxid + "s").form(strName));
+     output.append(" :  ");
+     output.append(String.valueOf(pdbStart));
+     output.append(" - ");
+     output.append(String.valueOf(pdbEnd));
+     output.append(NEWLINE).append(NEWLINE);
+     int matchedSeqCount = 0;
+     for (int j = 0; j < nochunks; j++)
+     {
+       // Print the first aligned sequence
+       output.append(new Format("%" + (maxid) + "s").form(seqName)).append(
+               " ");
+       for (int i = 0; i < len; i++)
+       {
+         if ((i + (j * len)) < seqRes.length())
+         {
+           output.append(seqRes.charAt(i + (j * len)));
+         }
+       }
+       output.append(NEWLINE);
+       output.append(new Format("%" + (maxid) + "s").form(" ")).append(" ");
+       // Print out the matching chars
+       for (int i = 0; i < len; i++)
+       {
+         try
+         {
++          char c1 = seqRes.charAt(i + (j * len));
++          char c2 = strRes.charAt(i + (j * len));
+           if ((i + (j * len)) < seqRes.length())
+           {
+             boolean sameChar = Comparison.isSameResidue(
+                     seqRes.charAt(i + (j * len)),
+                     strRes.charAt(i + (j * len)), false);
+             if (sameChar
+                     && !jalview.util.Comparison.isGap(seqRes.charAt(i
+                             + (j * len))))
+             {
+               matchedSeqCount++;
+               output.append("|");
+             }
+             else if (type.equals("pep"))
+             {
 -              if (ResidueProperties.getPAM250(seqRes.charAt(i + (j * len)),
 -                      strRes.charAt(i + (j * len))) > 0)
++              if (pam250.getPairwiseScore(c1, c2) > 0)
+               {
+                 output.append(".");
+               }
+               else
+               {
+                 output.append(" ");
+               }
+             }
+             else
+             {
+               output.append(" ");
+             }
+           }
+         } catch (IndexOutOfBoundsException e)
+         {
+           continue;
+         }
+       }
+       // Now print the second aligned sequence
+       output = output.append(NEWLINE);
+       output = output.append(new Format("%" + (maxid) + "s").form(strName))
+               .append(" ");
+       for (int i = 0; i < len; i++)
+       {
+         if ((i + (j * len)) < strRes.length())
+         {
+           output.append(strRes.charAt(i + (j * len)));
+         }
+       }
+       output.append(NEWLINE).append(NEWLINE);
+     }
+     float pid = (float) matchedSeqCount / seqRes.length() * 100;
+     output.append("Length of alignment = " + seqRes.length()).append(
+             NEWLINE);
+     output.append(new Format("Percentage ID = %2.2f").form(pid));
+     return output;
+   }
+   public static List<Phyre2SummaryPojo> parsePhyreCrudeList(String crudeList)
+   {
+     List<Phyre2SummaryPojo> phyre2Results = new ArrayList<Phyre2SummaryPojo>();
+     try (BufferedReader br = new BufferedReader(new FileReader(crudeList)))
+     {
+       String line;
+       while ((line = br.readLine()) != null)
+       {
+         String[] lineData = line.split(" ");
+         Phyre2SummaryPojo psp = new Phyre2SummaryPojo();
+         psp.setSerialNo(Integer.valueOf(lineData[0]));
+         psp.setTemplateId(lineData[1]);
+         psp.setConfidence(100 * Double.valueOf(lineData[2]));
+         psp.setPid(Integer.valueOf(lineData[3]));
+         psp.setAlignedRange(lineData[4] + " - " + lineData[5]);
+         // psp.setCoverage(coverage);
+         // psp.setTemplateSummary(templateSummary);
+         phyre2Results.add(psp);
+       }
+     } catch (Exception e)
+     {
+       e.printStackTrace();
+     }
+     return phyre2Results;
+   }
+   public static DefaultTableModel getTableModel(
+           List<Phyre2SummaryPojo> phyreResults)
+   {
+     if (phyreResults == null)
+     {
+       return null;
+     }
+     DefaultTableModel tableModel = new DefaultTableModel()
+     {
+       @Override
+       public boolean isCellEditable(int row, int column)
+       {
+         return false;
+       }
+       @Override
+       public Class<?> getColumnClass(int columnIndex)
+       {
+         switch (columnIndex)
+         {
+         case 0:
+           return Integer.class;
+         case 1:
+           return String.class;
+         case 2:
+           return String.class;
+         case 3:
+           return String.class;
+         case 4:
+           return Double.class;
+         case 5:
+           return Integer.class;
+         case 6:
+           return String.class;
+         default:
+           return String.class;
+         }
+       }
+     };
+     tableModel.addColumn("#");
+     tableModel.addColumn("Template");
+     tableModel.addColumn("Aligned Range");
+     tableModel.addColumn("Coverage");
+     tableModel.addColumn("Confidence");
+     tableModel.addColumn("%.i.d");
+     tableModel.addColumn("Template Information");
+     for (Phyre2SummaryPojo res : phyreResults)
+     {
+       tableModel.addRow(new Object[] { res.getSerialNo(),
+           res.getTemplateId(), res.getAlignedRange(), res.getCoverage(),
+           res.getConfidence(), res.getPid(), res.getTemplateSummary() }); 
+     }
+     return tableModel;
+   }
+   public static void configurePhyreResultTable(JTable phyreResultTable)
+   {
+     DecimalFormatTableCellRenderer idCellRender = new DecimalFormatTableCellRenderer(
+             true, 0);
+     DecimalFormatTableCellRenderer pidCellRender = new DecimalFormatTableCellRenderer(
+             true, 1);
+     DecimalFormatTableCellRenderer confidenceCellRender = new DecimalFormatTableCellRenderer(
+             true, 1);
+     phyreResultTable.getColumn("#").setMinWidth(20);
+     phyreResultTable.getColumn("#").setPreferredWidth(30);
+     phyreResultTable.getColumn("#").setMaxWidth(40);
+     phyreResultTable.getColumn("#").setCellRenderer(idCellRender);
+     phyreResultTable.getColumn("Template").setMinWidth(60);
+     phyreResultTable.getColumn("Template").setPreferredWidth(90);
+     phyreResultTable.getColumn("Template").setMaxWidth(150);
+     phyreResultTable.getColumn("Aligned Range").setMinWidth(80);
+     phyreResultTable.getColumn("Aligned Range").setPreferredWidth(80);
+     phyreResultTable.getColumn("Aligned Range").setMaxWidth(120);
+     phyreResultTable.getColumn("Coverage").setMinWidth(60);
+     phyreResultTable.getColumn("Coverage").setPreferredWidth(60);
+     phyreResultTable.getColumn("Coverage").setMaxWidth(90);
+     phyreResultTable.getColumn("Confidence").setMinWidth(60);
+     phyreResultTable.getColumn("Confidence").setPreferredWidth(60);
+     phyreResultTable.getColumn("Confidence").setMaxWidth(90);
+     phyreResultTable.getColumn("Confidence").setCellRenderer(
+             confidenceCellRender);
+     phyreResultTable.getColumn("%.i.d").setMinWidth(45);
+     phyreResultTable.getColumn("%.i.d").setPreferredWidth(450);
+     phyreResultTable.getColumn("%.i.d").setMaxWidth(65);
+     phyreResultTable.getColumn("%.i.d").setCellRenderer(pidCellRender);
+     phyreResultTable.getColumn("Template Information").setMinWidth(400);
+     phyreResultTable.getColumn("Template Information").setPreferredWidth(
+             600);
+     phyreResultTable.getColumn("Template Information").setMaxWidth(1500);
+   }
+ }
@@@ -31,6 -29,8 +31,7 @@@ import jalview.datamodel.SequenceI
  import jalview.io.StructureFile;
  import jalview.schemes.ResidueProperties;
  import jalview.structure.StructureMapping;
 -import jalview.structure.StructureMappingClient;
+ import jalview.structures.models.MappingOutputModel;
  import jalview.util.Comparison;
  import jalview.util.DBRefUtils;
  import jalview.util.Format;
@@@ -82,10 -79,12 +83,14 @@@ public class SiftsClient implements Sif
     */
    private static File mockSiftsFile;
  
+   private static final int UNASSIGNED = StructureMapping.UNASSIGNED; // -1
+   private static final int PDB_RES_POS = StructureMapping.PDB_RES_NUM_INDEX; // 0
    private Entry siftsEntry;
  
-   private StructureFile pdb;
++  private StructureFile structureFile;
 +
    private String pdbId;
  
    private String structId;
  
    private static final int BUFFER_SIZE = 4096;
  
-   public static final int UNASSIGNED = -1;
-   private static final int PDB_RES_POS = 0;
 +  private static final int PDB_ATOM_POS = 1;
 +
    private static final String NOT_OBSERVED = "Not_Observed";
  
    private static final String SIFTS_FTP_BASE_URL = "http://ftp.ebi.ac.uk/pub/databases/msd/sifts/xml/";
      }
    }
  
 +  /**
 +   * Get the leading integer part of a string that begins with an integer.
 +   * 
 +   * @param input
 +   *          - the string input to process
 +   * @param failValue
 +   *          - value returned if unsuccessful
 +   * @return
 +   */
 +  static int getLeadingIntegerValue(String input, int failValue)
 +  {
 +    if (input == null)
 +    {
 +      return failValue;
 +    }
 +    String[] parts = input.split("(?=\\D)(?<=\\d)");
 +    if (parts != null && parts.length > 0 && parts[0].matches("[0-9]+"))
 +    {
 +      return Integer.valueOf(parts[0]);
 +    }
 +    return failValue;
 +  }
 +
 +
 +  /**
 +   * 
 +   * @param chainId
 +   *          Target chain to populate mapping of its atom positions.
 +   * @param mapping
 +   *          Two dimension array of residue index versus atom position
 +   * @throws IllegalArgumentException
 +   *           Thrown if chainId or mapping is null
 +   * @throws SiftsException
 +   */
 +  void populateAtomPositions(String chainId, Map<Integer, int[]> mapping)
 +          throws IllegalArgumentException, SiftsException
 +  {
 +    try
 +    {
-       PDBChain chain = pdb.findChain(chainId);
++      PDBChain chain = structureFile.findChain(chainId);
 +
 +      if (chain == null || mapping == null)
 +      {
 +        throw new IllegalArgumentException(
 +                "Chain id or mapping must not be null.");
 +      }
 +      for (int[] map : mapping.values())
 +      {
 +        if (map[PDB_RES_POS] != UNASSIGNED)
 +        {
 +          map[PDB_ATOM_POS] = getAtomIndex(map[PDB_RES_POS], chain.atoms);
 +        }
 +      }
 +    } catch (NullPointerException e)
 +    {
 +      throw new SiftsException(e.getMessage());
 +    } catch (Exception e)
 +    {
 +      throw new SiftsException(e.getMessage());
 +    }
 +  }
 +
 +  /**
 +   * 
 +   * @param residueIndex
 +   *          The residue index used for the search
 +   * @param atoms
 +   *          A collection of Atom to search
 +   * @return atom position for the given residue index
 +   */
 +  int getAtomIndex(int residueIndex, Collection<Atom> atoms)
 +  {
 +    if (atoms == null)
 +    {
 +      throw new IllegalArgumentException(
 +              "atoms collection must not be null!");
 +    }
 +    for (Atom atom : atoms)
 +    {
 +      if (atom.resNumber == residueIndex)
 +      {
 +        return atom.atomIndex;
 +      }
 +    }
 +    return UNASSIGNED;
 +  }
  
    /**
     * Checks if the residue instance is marked 'Not_observed' or not
    }
  
    @Override
-   public StringBuilder getMappingOutput(MappingOutputPojo mp)
 -  public StringBuffer getMappingOutput(MappingOutputModel mp)
 -          throws StructureMappingException
++  public StringBuilder getMappingOutput(MappingOutputModel mp)
 +          throws SiftsException
    {
      String seqRes = mp.getSeqResidue();
      String seqName = mp.getSeqName();
      mockSiftsFile = file;
    }
  
++
  }
@@@ -116,5 -117,56 +117,57 @@@ public class JmolViewerTes
      }
    }
  
 +
+   @Test(groups = { "Functional, Network" })
+   public void testStructureLoadingViaURL()
+   {
+     Cache.setProperty(Preferences.STRUCTURE_DISPLAY, ViewerType.JMOL.name());
+     String inFile = "http://www.jalview.org/builds/develop/examples/3W5V.pdb";
+     AlignFrame af = new jalview.io.FileLoader().LoadFileWaitTillLoaded(
+             inFile, DataSourceType.URL);
+     assertTrue("Didn't read input file " + inFile, af != null);
+     for (SequenceI sq : af.getViewport().getAlignment().getSequences())
+     {
+       SequenceI dsq = sq.getDatasetSequence();
+       while (dsq.getDatasetSequence() != null)
+       {
+         dsq = dsq.getDatasetSequence();
+       }
+       if (dsq.getAllPDBEntries() != null
+               && dsq.getAllPDBEntries().size() > 0)
+       {
+         for (int q = 0; q < dsq.getAllPDBEntries().size(); q++)
+         {
+           final StructureViewer structureViewer = new StructureViewer(af
+                   .getViewport().getStructureSelectionManager());
+           structureViewer.setViewerType(ViewerType.JMOL);
+           JalviewStructureDisplayI jmolViewer = structureViewer
+                   .viewStructures(dsq.getAllPDBEntries().elementAt(q),
+                           new SequenceI[] { sq }, af.getCurrentView()
+                                   .getAlignPanel());
+           /*
+           * Wait for viewer load thread to complete
+           */
+           try
+           {
+             while (!jmolViewer.getBinding().isFinishedInit())
+             {
+               Thread.sleep(500);
+             }
+           } catch (InterruptedException e)
+           {
+           }
+           // System.out.println(">>>>>>>>>>>>>>>>> "
+           // + jmolViewer.getBinding().getPdbFile());
+           String[] expectedModelFiles = new String[] { "http://www.jalview.org/builds/develop/examples/3W5V.pdb" };
+           String[] actualModelFiles = jmolViewer.getBinding().getStructureFiles();
+           Assert.assertEqualsNoOrder(actualModelFiles, expectedModelFiles);
+           jmolViewer.closeViewer(true);
+           // todo: break here means only once through this loop?
+           break;
+         }
+         break;
+       }
+     }
+   }
  }
@@@ -24,9 -24,14 +24,14 @@@ import static org.testng.AssertJUnit.as
  import static org.testng.AssertJUnit.assertTrue;
  
  import jalview.datamodel.AlignmentI;
 -import jalview.datamodel.ColumnSelection;
+ import jalview.datamodel.DynamicData;
 +import jalview.datamodel.HiddenColumns;
+ import jalview.datamodel.PDBEntry;
+ import jalview.datamodel.PDBEntry.Type;
+ import jalview.datamodel.SequenceI;
  import jalview.gui.JvOptionPane;
  import jalview.io.AnnotationFile.ViewDef;
+ import jalview.structure.StructureSelectionManager;
  
  import java.io.File;
  import java.util.Hashtable;
@@@ -165,4 -173,354 +175,354 @@@ public class AnnotationFileIOTes
              + "\nCouldn't complete Annotation file roundtrip input/output/input test for '"
              + annotFile + "'.");
    }
+   @BeforeClass(alwaysRun = true)
+   void testProcessStructModel()
+   {
+     try
+     {
+       ssm = StructureSelectionManager.getStructureSelectionManager();
+     } catch (NullPointerException e)
+     {
+       ssm = new StructureSelectionManager();
+     }
+     File alignmentFile = new File(
+             "examples/testdata/phyre2results/56da5616b4559c93/allhits.fasta");
+     String annotationFile = "examples/testdata/phyre2results/56da5616b4559c93/allhits.ann";
 -    ColumnSelection cs = new ColumnSelection();
++    HiddenColumns cs = new HiddenColumns();
+     al = readAlignmentFile(alignmentFile);
+     boolean annotationRead = new AnnotationFile().readAnnotationFile(al,
+             cs, annotationFile, DataSourceType.FILE);
+     Assert.assertTrue(annotationRead);
+     System.out.println("bla");
+   }
+   
+   @Test(
+     groups = { "Functional" },
+     dataProvider = "phyre2ModelPDBEntryDataProvider")
+   void testSequence_PDBEntryAssociation(String[] structModelHeader, String baseDir,
+           String structModelDataStr)
+   {
+     String structModelData[] = structModelDataStr.split("\t");
+     String templateSeq = structModelData[1];
+     String pdbId = structModelData[2];
+     SequenceI testSeq = al.findName(templateSeq);
+     Assert.assertNotNull(testSeq);
+     PDBEntry actualPDBEntry = testSeq.getDatasetSequence().getPDBEntry(
+             pdbId);
+     Assert.assertNotNull(actualPDBEntry);
+     PDBEntry expectedPDBEntry = new PDBEntry(pdbId, " ", Type.PDB, baseDir
+             + pdbId);
+     List<DynamicData> phyreDD = AnnotationFile
+             .generatePhyreDynamicDataList(structModelHeader,
+                     structModelData);
+     expectedPDBEntry.setProperty("DYNAMIC_DATA_PHYRE2", phyreDD);
+     Assert.assertEquals(actualPDBEntry, expectedPDBEntry);
+   }
+   @Test(
+     groups = { "Functional" },
+     dataProvider = "phyre2ModelMappingDataProvider")
+   void testPhyre2ModelRegistration(String phyre2ModelFile,
+           String expectedPhyre2FastaMappingFile)
+   {
+     String actualFastaMappingFile = ssm
+             .getPhyre2FastaFileFor(phyre2ModelFile);
+     Assert.assertNotNull(actualFastaMappingFile);
+     Assert.assertEquals(actualFastaMappingFile,
+             expectedPhyre2FastaMappingFile);
+   }
+   @Test(groups = { "Functional" }, dataProvider = "FilePathProvider")
+   void testResolveAbsolutePath(String caseDescription, String suppliedPath,
+           String baseURI, String expectedURI)
+   {
+     System.out.println(">>>> Testing Case - " + caseDescription);
+     String actualURI = AnnotationFile.resolveAbsolutePath(suppliedPath,
+             baseURI);
+     Assert.assertEquals(actualURI, expectedURI);
+   }
+   
+   @DataProvider(name = "phyre2ModelPDBEntryDataProvider")
+   public static Object[][] phyre2ModelPDBEntryDataProvider()
+   {
+     String[] structModelHeader = new String[] { "QUERY_SEQ",
+             "TEMPLATE_SEQ", "MODEL_FILE", "MAPPING_FILE", "Confidence",
+             "% I.D", "Aligned Range", "Other Information"};
+     String baseDir = "examples/testdata/phyre2results/56da5616b4559c93/";
+     
+     return new Object[][] {
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\tc4n58A_\tc4n58A_.1.pdb\tc4n58A_.1.fasta\t1\t54\t48-143\t<b>PDB Header: </b>Hyrolase<br><b>Chain: "
+                     + "</b>A<br><b>PDB Molecule: </b>Pectocin m2<br> <b>PDB Title: </b>Crystal structure of pectocin m2 at 1.86 amgtroms" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1a70a_\td1a70a_.2.pdb\td1a70a_.2.fasta\t1\t71\t48-144\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1offa_\td1offa_.3.pdb\td1offa_.3.fasta\t1\t73\t48-142\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1frra_\td1frra_.4.pdb\td1frra_.4.fasta\t0.999\t62\t49-142\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1pfda_\td1pfda_.5.pdb\td1pfda_.5.fasta\t0.999\t70\t48-143\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1frda_\td1frda_.6.pdb\td1frda_.6.fasta\t0.999\t50\t48-143\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1fxia_\td1fxia_.7.pdb\td1fxia_.7.fasta\t0.999\t62\t48-142\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1gaqb_\td1gaqb_.8.pdb\td1gaqb_.8.fasta\t0.999\t71\t48-144\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1iuea_\td1iuea_.9.pdb\td1iuea_.9.fasta\t0.999\t48\t48-142\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1awda_\td1awda_.10.pdb\td1awda_.10.fasta\t0.999\t68\t50-142\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1wria_\td1wria_.11.pdb\td1wria_.11.fasta\t0.999\t59\t49-142\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1czpa_\td1czpa_.12.pdb\td1czpa_.12.fasta\t0.999\t64\t48-142\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td2cjoa_\td2cjoa_.13.pdb\td2cjoa_.13.fasta\t0.999\t63\t48-142\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td4fxca_\td4fxca_.14.pdb\td4fxca_.14.fasta\t0.999\t64\t48-142\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\tc4itkA_\tc4itkA_.15.pdb\tc4itkA_.15.fasta\t0.999\t57\t50-142\t<b>PDB Header: </b>Electron transport<br>"
+                     + "<b>Chain: </b>A<br><b>PDB Molecule: </b>Apoferredoxin <br><b>PDB Title: </b>The structure of c.reinhardtii ferredoxin 2" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\tc1krhA_\tc1krhA_.16.pdb\tc1krhA_.16.fasta\t0.999\t25\t48-142\t<b>PDB Header: </b>Oxidoreductase<br><b>Chain: "
+                     + "</b>A<br><b>PDB Molecule: </b>Benzoate 1,2-deoxygenase reductase <br> <b>PDB Title: </b>X-ray structure of benzoate "
+                     + "deoxygenate reductase" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1krha3\td1krha3.17.pdb\td1krha3.17.fasta\t0.999\t24\t48-143\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin domains from multi domain proteins" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\td1jq4a_\td1jq4a_.18.pdb\td1jq4a_.18.fasta\t0.999\t29\t47-138\t<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br>"
+                     + "<b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin domains from multi domain proteins" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\tc4wqmA_\tc4wqmA_.19.pdb\tc4wqmA_.19.fasta\t0.999\t28\t49-144\t<b>PDB header: </b>Oxidoreductase<br><b>Chain: "
+                     + "</b>A<br><b>PDB Molecule: </b>Toluene-4-monooxygenase electron transfer component<br><b>PDB Title: </b>Structure of the "
+                     + "toluene 4-monooxygenase nah oxidoreductase t4mof,2 k270s k271s variant" },
+         {
+             structModelHeader,
+             baseDir,
+             "FER_CAPAN_1-144\tc2piaA_\tc2piaA_.20.pdb\tc2piaA_.20.fasta\t0.999\t22\t1-136\t<b>PDB header: </b>Reductase<br><b>Chain: "
+                     + "</b>A<br><b>PDB Molecule: </b>Phthalate deoxygenase reductase<br><b>PDB Title: </b>Phthalate deoxygenate reductase: a"
+                     + " modular structure for2 electron transfer from pyridine nucleotides to [2fe-2s]" }
+     };
+   }
+   @DataProvider(name = "phyre2ModelMappingDataProvider")
+   public static Object[][] phyre2ModelMappingDataProvider()
+   {
+     return new Object[][] {
+         { "examples/testdata/phyre2results/56da5616b4559c93/c4n58A_.1.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/c4n58A_.1.fasta" },
+         { "examples/testdata/phyre2results/56da5616b4559c93/d1a70a_.2.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1a70a_.2.fasta" },
+         { "examples/testdata/phyre2results/56da5616b4559c93/d1offa_.3.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1offa_.3.fasta" },
+         { "examples/testdata/phyre2results/56da5616b4559c93/d1frra_.4.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1frra_.4.fasta" },
+         { "examples/testdata/phyre2results/56da5616b4559c93/d1pfda_.5.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1pfda_.5.fasta" },
+         { "examples/testdata/phyre2results/56da5616b4559c93/d1frda_.6.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1frda_.6.fasta" },
+         { "examples/testdata/phyre2results/56da5616b4559c93/d1fxia_.7.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1fxia_.7.fasta" },
+         { "examples/testdata/phyre2results/56da5616b4559c93/d1gaqb_.8.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1gaqb_.8.fasta" },
+         { "examples/testdata/phyre2results/56da5616b4559c93/d1iuea_.9.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1iuea_.9.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/d1awda_.10.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1awda_.10.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/d1wria_.11.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1wria_.11.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/d1czpa_.12.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1czpa_.12.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/d2cjoa_.13.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d2cjoa_.13.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/d4fxca_.14.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d4fxca_.14.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/c4itkA_.15.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/c4itkA_.15.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/c1krhA_.16.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/c1krhA_.16.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/d1krha3.17.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1krha3.17.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/d1jq4a_.18.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/d1jq4a_.18.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/c4wqmA_.19.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/c4wqmA_.19.fasta" },
+         {
+             "examples/testdata/phyre2results/56da5616b4559c93/c2piaA_.20.pdb",
+             "examples/testdata/phyre2results/56da5616b4559c93/c2piaA_.20.fasta" }
+     };
+   }
+   @DataProvider(name = "phyre2InfoHTMLTableDataProvider")
+   public static Object[][] phyre2InfoHTMLTableDataProvider()
+   {
+     return new Object[][] {
+         {
+             "STRUCTMODEL Annotation with no headear information provided",
+             null,
+             new String[] { "FER_CAPAN_1-144", "d1a70a_ d1a70a_.2.pdb",
+                 "d1a70a_.2.fasta", }, "" },
+         {
+             "STRUCTMODEL Annotation with complete compulsary data and headear information provided",
+             new String[] { "HEADER_STRUCT_MODEL", "QUERY_SEQ",
+                 "TEMPLATE_SEQ", "MODEL_FILE", "MAPPING_FILE" },
+             new String[] { "FER_CAPAN_1-144", "d1a70a_ d1a70a_.2.pdb",
+                 "d1a70a_.2.fasta", },
+             "<html><table border=\"1\" width=100%><tr><td colspan=\"2\"><strong>Phyre2 Template Info</strong></td></tr></table></html>" },
+         {
+             "STRUCTMODEL Annotation with complete compulsary data and headear information provided",
+             new String[] { "HEADER_STRUCT_MODEL", "QUERY_SEQ",
+                 "TEMPLATE_SEQ", "MODEL_FILE", "MAPPING_FILE", "Confidence",
+                 "% I.D", "Aligned Range", "Other Information", "Coverage" },
+             new String[] {
+                 "FER_CAPAN_1-144",
+                 "d1a70a_ d1a70a_.2.pdb",
+                 "d1a70a_.2.fasta",
+                 "1",
+                 "71",
+                 "48-144",
+                 "<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br><b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+             "<html><table border=\"1\" width=100%><tr><td colspan=\"2\"><strong>Phyre2 Template Info</strong></td></tr><tr><td>MAPPING_FILE</td><td>71</td></tr><tr><td>Confidence</td><td>48-144</td></tr><tr><td>% I.D</td><td><b>Fold: </b>Beta-Grasp (ubiquitin-like)<br><b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related</td></tr></table></html>" } };
+   }
+   @DataProvider(name = "StructModelHtmlDataProvider")
+   public static Object[][] IsGenerateStructInfoHtmlDataProvider()
+   {
+     return new Object[][] {
+         { "STRUCTMODEL Annotation with no headear or data provided", null,
+             null, false },
+         {
+             "STRUCTMODEL Annotation with headear information and no data column provided",
+             new String[] { "HEADER_STRUCT_MODEL", "QUERY_SEQ" }, null,
+             false },
+         {
+             "STRUCTMODEL Annotation with no headear information provided",
+             null,
+             new String[] { "FER_CAPAN_1-144", "d1a70a_ d1a70a_.2.pdb",
+                 "d1a70a_.2.fasta", }, false },
+         {
+             "STRUCTMODEL Annotation with only two headear information and two data column provided",
+             new String[] { "HEADER_STRUCT_MODEL", "QUERY_SEQ" },
+             new String[] { "FER_CAPAN_1-144", "d1a70a_" }, false },
+         {
+             "STRUCTMODEL Annotation with complete compulsary data and headear information provided",
+             new String[] { "HEADER_STRUCT_MODEL", "QUERY_SEQ",
+                 "TEMPLATE_SEQ", "MODEL_FILE", "MAPPING_FILE" },
+             new String[] { "FER_CAPAN_1-144", "d1a70a_", "d1a70a_.2.pdb",
+                 "d1a70a_.2.fasta", }, true },
+         {
+             "STRUCTMODEL Annotation with complete compulsary data and headear information provided",
+             new String[] { "HEADER_STRUCT_MODEL", "QUERY_SEQ",
+                 "TEMPLATE_SEQ", "MODEL_FILE", "MAPPING_FILE", "Confidence",
+                 "% I.D", "Aligned Range", "Other Information", "Coverage" },
+             new String[] {
+                 "FER_CAPAN_1-144",
+                 "d1a70a_",
+                 "d1a70a_.2.pdb",
+                 "d1a70a_.2.fasta",
+                 "1",
+                 "71",
+                 "48-144",
+                 "<b>Fold: </b>Beta-Grasp (ubiquitin-like)<br><b>Superfamily: </b>2Fe-2S ferredoxin-like<br><b>Family: </b>2Fe-2S ferredoxin-related" },
+             true } };
+   }
+   @DataProvider(name = "FilePathProvider")
+   public static Object[][] filePathProvider()
+   {
+     return new Object[][] {
+         { "relative local file path resolution", "c4n58A_.1.pdb", "",
+             "c4n58A_.1.pdb" },
+         { "relative local file path resolution", "c4n58A_.1.pdb",
+             "/examples/testdata/phyre2results/",
+             "/examples/testdata/phyre2results/c4n58A_.1.pdb" },
+         {
+             "relative URL path resolution",
+             "c4n58A_.1.pdb",
+             "http://www.jalview.org/builds/develop/examples/testdata/phyre2results/",
+             "http://www.jalview.org/builds/develop/examples/testdata/phyre2results/c4n58A_.1.pdb" },
+         {
+             "Absolute local file path resolution",
+             "/examples/testdata/phyre2results_xx/c4n58A_.1.pdb",
+             "/examples/testdata/phyre2results/",
+             "/examples/testdata/phyre2results_xx/c4n58A_.1.pdb" },
+         {
+             "Absolute URL path resolution",
+             "http://www.jalview.org/builds/develop/another_directory/c4n58A_.1.pdb",
+             "http://www.jalview.org/builds/develop/examples/testdata/phyre2results/",
+             "http://www.jalview.org/builds/develop/another_directory/c4n58A_.1.pdb" } };
+   }
  }
@@@ -332,18 -334,18 +334,20 @@@ public class SiftsClientTes
  
    @Test(
  groups = { "Network" },
-     expectedExceptions = SiftsException.class)
+     expectedExceptions = StructureMappingException.class)
    private void populateAtomPositionsNullTest1()
-           throws IllegalArgumentException, SiftsException
 -          throws IllegalArgumentException, StructureMappingException
++          throws IllegalArgumentException, StructureMappingException,
++          SiftsException
    {
      siftsClient.populateAtomPositions(null, null);
    }
  
    @Test(
  groups = { "Network" },
-     expectedExceptions = SiftsException.class)
+     expectedExceptions = StructureMappingException.class)
    private void populateAtomPositionsNullTest2()
-           throws IllegalArgumentException, SiftsException
 -          throws IllegalArgumentException, StructureMappingException
++          throws IllegalArgumentException, StructureMappingException,
++          SiftsException
    {
      siftsClient.populateAtomPositions("A", null);
    }
@@@ -392,10 -394,11 +396,11 @@@ groups = { "Network" }
    }
  
    @Test(groups = { "Network" })
-   public void getSiftsStructureMappingTest() throws SiftsException
+   public void getSiftsStructureMappingTest()
+           throws StructureMappingException, Exception
    {
      Assert.assertTrue(SiftsSettings.isMapWithSifts());
 -    StructureMapping strucMapping = siftsClient.getStructureMapping(
 +    StructureMapping strucMapping = siftsClient.getSiftsStructureMapping(
              testSeq, testPDBId, "A");
      String expectedMappingOutput = "\nSequence ⟷ Structure mapping details\n"
              + "Method: SIFTS\n\n"