Repository 'alphafold2'
hg clone https://toolshed.g2.bx.psu.edu/repos/galaxy-australia/alphafold2

Changeset 20:6ab1a261520a (2024-07-28)
Previous changeset 19:2f7702fd0a4c (2024-05-08) Next changeset 21:e7f1b552a695 (2024-10-29)
Commit message:
planemo upload for repository https://github.com/usegalaxy-au/tools-au commit c3a90eb12ada44d477541baa4dd6182be29cd554-dirty
modified:
alphafold.xml
macro_output.xml
macro_test_output.xml
scripts/outputs.py
added:
scripts/alphafold.html
removed:
alphafold.html
b
diff -r 2f7702fd0a4c -r 6ab1a261520a alphafold.html
--- a/alphafold.html Wed May 08 06:26:55 2024 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
[
b'@@ -1,656 +0,0 @@\n-<!DOCTYPE html>\n-<html lang="en" dir="ltr">\n-\n-  <head>\n-    <meta charset="utf-8">\n-    <meta http-equiv="X-UA-Compatible" content="IE=edge">\n-    <meta name="viewport" content="width=device-width, initial-scale=1">\n-\n-    <title> Alphafold structure prediction </title>\n-\n-    <link rel="preconnect" href="https://fonts.googleapis.com">\n-    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n-    <link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap" rel="stylesheet">\n-    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">\n-    <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.0/chroma.min.js" integrity="sha512-yocoLferfPbcwpCMr8v/B0AB4SWpJlouBwgE0D3ZHaiP1nuu5djZclFEIj9znuqghaZ3tdCMRrreLoM8km+jIQ==" crossorigin="anonymous"></script>\n-\n-    <style type="text/css">\n-      * {\n-        margin: 0;\n-        padding: 0;\n-      }\n-      html, body {\n-        width: 100%;\n-        font-size: 1rem;\n-      }\n-      body {\n-        font-family: \'Ubuntu\', sans-serif;\n-      }\n-      canvas {\n-        background-color: white;\n-      }\n-      h1, h2, h3, h4, h5, h6 {\n-        color: dodgerblue;\n-        text-align: center;\n-        font-weight: lighter;\n-      }\n-      h1 {\n-        margin: 2rem;\n-        font-size: 3rem;\n-      }\n-      h2 {\n-        font-size: 2rem;\n-        margin-top: 1rem;\n-        margin-bottom: .5rem;\n-      }\n-      button.btn {\n-        color: #ccc;\n-        margin: 1rem;\n-        padding: .5rem;\n-        font-size: 1rem;\n-        min-width: 4rem;\n-        border: none;\n-        border-radius: .5rem;\n-        background-color: grey;\n-        transition-duration: 0.25s;\n-        cursor: pointer;\n-      }\n-      button.btn.selected {\n-        color: #eee;\n-        background-color: dodgerblue;\n-      }\n-      button.btn.green {\n-        color: #eee;\n-        background-color: #10941f;\n-      }\n-      button.btn:focus {\n-        outline: none;\n-        color: inherit;\n-      }\n-      button.btn:hover {\n-        color: white;\n-        box-shadow: 0 0 10px dodgerblue;\n-      }\n-      button.btn.green:hover {\n-        color: white;\n-        box-shadow: 0 0 10px limegreen;\n-      }\n-      .main {\n-        min-height: 90vh;\n-        position: relative;\n-      }\n-      .flex {\n-        display: flex;\n-        justify-content: center;\n-        align-items: center;\n-        padding: 1rem;\n-      }\n-      .col {\n-        flex-direction: column;\n-        flex-grow: 0;\n-      }\n-      .controls {\n-        padding-bottom: 10vh;\n-      }\n-      .box {\n-        padding: .5rem 1rem;\n-        margin: .5rem auto;\n-        width: fit-content;\n-        border-radius: 1rem;\n-      }\n-      .mono {\n-        font-family: monospace;\n-        color: #555;\n-        background-color: #ddd;\n-        padding: .25rem;\n-        border-radius: .25rem;\n-      }\n-      .space-1 {\n-        line-height: 1.2;\n-      }\n-      .space-2 {\n-        line-height: 1.5;\n-      }\n-      .relative {\n-        position: relative;\n-      }\n-      .legend {\n-        max-width: 350px;\n-      }\n-      .legend .scale {\n-        display: flex;\n-        flex-direction: column;\n-        align-items: center;\n-      }\n-      .legend .color {\n-        width: 150px;\n-        height: 30px;\n-        justify-content: space-between;\n-        background: linear-gradient(\n-          90deg,\n-          rgba(255,55,0,1)   0%,\n-          rgba(216,224,6,1)  33%,\n-          rgba(34,213,238,1) 66%,\n-          rgba(3,30,148,1)   100%\n-          );\n-      }\n-      .legend .ticks {\n-        margin-top: -1rem;\n-        width: 180px;\n-        justify-content: space-between;\n-      }\n-      #ngl-root-parent {\n-        width: 40vw;\n-        height: 30vw;\n-        margin: auto;\n-        position: relative;\n-      }\n-      #ngl-root {\n-        width: '..b' if (reps.length) {\n-        state.representations = {};\n-      } else {\n-        reps = [DEFAULT_REPRESENTATION];\n-      }\n-\n-      // Load PDB entry\n-      return stage.loadFile(uri(state.model)).then( (o) => {\n-        state.modelObject = o;\n-        reps.forEach( (r) => addModelRepresentation(r) );\n-        stage.setSpin(state.spin);\n-        o.autoView();\n-        setLoading(0);\n-      })\n-    }\n-\n-    // Representations ---------------------------------------------------------\n-\n-    const toggleModelRepresentation = (rep) => {\n-      rep in state.representations ?\n-        removeModelRepresentation(rep)\n-        : addModelRepresentation(rep)\n-    }\n-\n-    const addModelRepresentation = (rep) => {\n-      state.representations[rep] =\n-        state.modelObject.addRepresentation(rep, {colorScheme: COLORSCHEME});\n-      updateButtons();\n-    }\n-\n-    const removeModelRepresentation = (rep) => {\n-      o = state.representations[rep];\n-      state.modelObject.removeRepresentation(o);\n-      delete state.representations[rep];\n-      updateButtons();\n-    }\n-\n-    const clearModelRepresentations = () => {\n-      state.modelObject && state.modelObject.removeAllRepresentations();\n-      state.representations = {};\n-    }\n-\n-    // Actions -----------------------------------------------------------------\n-\n-    const toggleDark = () => {\n-      state.darkMode = !state.darkMode;\n-      stage.setParameters({\n-        backgroundColor: state.darkMode ? \'black\' : \'white\',\n-      });\n-      const btn = document.querySelector(\'#btn-toggle-dark\');\n-      btn && btn.classList.toggle(\'selected\');\n-    }\n-\n-    const setLoading = (state) => {\n-      document.getElementById(\'ngl-loading\')\n-        .style.display = state ? \'flex\' : \'none\';\n-      state.loading = state;\n-    }\n-\n-    const toggleSpin = () => {\n-      stage.toggleSpin();\n-      const btn = document.querySelector(\'#btn-toggle-spin\');\n-      btn && btn.classList.toggle(\'selected\');\n-      state.spin = !state.spin;\n-    }\n-\n-    const downloadPng = () => {\n-      const params = {\n-        factor: 3,\n-        antialias: true,\n-      }\n-      stage.makeImage(params).then( (blob) => {\n-        const name = MODELS[state.model].replace(\'.pdb\', \'.png\');\n-        const url = URL.createObjectURL(blob);\n-        makeDownload(url, name);\n-      })\n-    }\n-\n-    const downloadPdb = () => {\n-      const url = uri(state.model);\n-      const name = `alphafold_${MODELS[state.model]}`;\n-      makeDownload(url, name);\n-    }\n-\n-    const makeDownload = (url, name) => {\n-      // Will not work with cross-origin urls (i.e. during development)\n-      console.log(`Creating file download for ${name}, href ${url}`);\n-      const saveLink = document.createElement(\'a\');\n-      saveLink.href = url;\n-      saveLink.download = name;\n-      document.body.appendChild(saveLink);\n-      saveLink.dispatchEvent(\n-        new MouseEvent(\'click\', {\n-          bubbles: true,\n-          cancelable: true,\n-          view: window\n-        })\n-      );\n-      document.body.removeChild(saveLink);\n-    }\n-\n-    const updateButtons = () => {\n-      MODELS.forEach( (name, i) => {\n-        const id = `#btn-${name.replace(\'.pdb\', \'\')}`;\n-        const btn = document.querySelector(id);\n-        if (!btn) return\n-        i == state.model ?\n-          btn.classList.add(\'selected\')\n-          : btn.classList.remove(\'selected\');\n-      })\n-\n-      REPRESENTATIONS.forEach( (name) => {\n-        const id = `#btn-${name}`.replace(\'+\', \'-\');\n-        const btn = document.querySelector(id);\n-        if (!btn) return\n-        if (name in state.representations) {\n-          btn.classList.add(\'selected\')\n-        } else {\n-          btn.classList.remove(\'selected\');\n-        }\n-      });\n-\n-      // Show "Nothing to display" if no representations are selected\n-      document.querySelector(\'#ngl-nothing\').style.display =\n-        Object.keys(state.representations).length ?\n-        \'none\'\n-        : \'block\';\n-    }\n-\n-  </script>\n-\n-</html>\n'
b
diff -r 2f7702fd0a4c -r 6ab1a261520a alphafold.xml
--- a/alphafold.xml Wed May 08 06:26:55 2024 +0000
+++ b/alphafold.xml Sun Jul 28 20:09:55 2024 +0000
[
b'@@ -1,9 +1,9 @@\n-<tool id="alphafold" name="Alphafold 2" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="20.01">\n+<tool id="alphafold" name="Alphafold 2" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="22.05">\n     <description> - AI-guided 3D structural prediction of proteins</description>\n     <macros>\n-      <token name="@TOOL_VERSION@">2.3.1</token>\n+      <token name="@TOOL_VERSION@">2.3.2</token>\n       <token name="@TOOL_MINOR_VERSION@">2.3</token>\n-      <token name="@VERSION_SUFFIX@">5</token>\n+      <token name="@VERSION_SUFFIX@">0</token>\n       <import>macro_output.xml</import>\n       <import>macro_test_output.xml</import>\n     </macros>\n@@ -17,12 +17,12 @@\n       <xref type="bio.tools">alphafold_2</xref>\n     </xrefs>\n     <requirements>\n-        <container type="docker">neoformit/alphafold:v2.3.1_2</container>\n+        <container type="docker">neoformit/alphafold:v2.3.2_0</container>\n     </requirements>\n     <required_files>\n         <include path="scripts/outputs.py" />\n         <include path="scripts/validate_fasta.py" />\n-        <include path="alphafold.html" />\n+        <include path="scripts/alphafold.html" />\n     </required_files>\n     <command detect_errors="exit_code"><![CDATA[\n \n@@ -46,7 +46,7 @@\n && python3 \'$__tool_directory__/scripts/validate_fasta.py\' input.fasta\n --min_length \\${ALPHAFOLD_AA_LENGTH_MIN:-0}\n --max_length \\${ALPHAFOLD_AA_LENGTH_MAX:-0}\n-#if $model_preset == \'multimer\':\n+#if $model_preset.selection == \'multimer\':\n --multimer\n --max-sequences \\${ALPHAFOLD_MAX_SEQUENCES:-10}\n #end if\n@@ -60,7 +60,7 @@\n ## Run AlphaFold  -------------------------------------------------------------\n #if os.environ.get(\'PLANEMO_TESTING\'):\n     ## Run in testing mode (mocks a successful AlphaFold run by copying outputs)\n-    && echo "Creating dummy outputs for model_preset=$model_preset..."\n+    && echo "Creating dummy outputs for model_preset=$model_preset.selection..."\n     && bash \'$__tool_directory__/scripts/mock_alphafold.sh\' $model_preset\n #else:\n     ## Run AlphaFold\n@@ -68,7 +68,7 @@\n         --fasta_paths alphafold.fasta\n         --output_dir output\n         --data_dir \\${ALPHAFOLD_DB:-/data}/@TOOL_MINOR_VERSION@/\n-        --model_preset=$model_preset\n+        --model_preset=$model_preset.selection\n \n         ## Set reference database paths\n         --uniref90_database_path   \\${ALPHAFOLD_DB:-/data}/@TOOL_MINOR_VERSION@/uniref90/uniref90.fasta\n@@ -83,21 +83,33 @@\n         --small_bfd_database_path  \\${ALPHAFOLD_DB:-/data}/@TOOL_MINOR_VERSION@/small_bfd/bfd-first_non_consensus_sequences.fasta\n         #end if\n \n-        #if $max_template_date:\n-        --max_template_date=$max_template_date\n+        #if $advanced.max_template_date:\n+        --max_template_date=$advanced.max_template_date\n         #else\n         --max_template_date=\\$TODAY\n         #end if\n \n-        --use_gpu_relax=\\${ALPHAFOLD_USE_GPU:-True}  ## introduced in v2.1.2\n+        --use_gpu_relax=\\${ALPHAFOLD_USE_GPU:-True}\n \n-        #if $model_preset == \'multimer\':\n+        #if $model_preset.selection == \'multimer\':\n         --pdb_seqres_database_path=\\${ALPHAFOLD_DB:-/data}/@TOOL_MINOR_VERSION@/pdb_seqres/pdb_seqres.txt\n         --uniprot_database_path=\\${ALPHAFOLD_DB:-/data}/@TOOL_MINOR_VERSION@/uniprot/uniprot.fasta\n-        --num_multimer_predictions_per_model=1  ## introduced in v2.2.0\n+        --num_multimer_predictions_per_model=$model_preset.num_multimer_predictions_per_model\n         #else\n         --pdb70_database_path \\${ALPHAFOLD_DB:-/data}/@TOOL_MINOR_VERSION@/pdb70/pdb70\n         #end if\n+\n+        ## Galaxy-specific options --------------------------------------------\n+        ## See https://github.com/neoformit/alphafold/tree/release_2.3.2_galaxy\n+        #if $advanced.disable_amber_relax:\n+        --disable_amber_relax\n+        #end if\n+\n+        #if $advanced.limit_model_outputs:\n+        --output_models=$limit_model_outputs\n+        #end if\n+        ## End Galaxy-specific options ---------------'..b'ram name="model_preset|selection" value="monomer_ptm"/>\n             <param name="outputs|plots" value="true"/>\n             <param name="outputs|confidence_scores" value="true"/>\n             <param name="outputs|plddts" value="true"/>\n@@ -303,19 +359,21 @@\n                 <param name="input_mode" value="history"/>\n                 <param name="fasta_file" value="multimer.fasta"/>\n             </conditional>\n-            <param name="model_preset" value="multimer"/>\n+            <param name="model_preset|selection" value="multimer"/>\n             <param name="outputs|plots" value="true"/>\n             <param name="outputs|confidence_scores" value="true"/>\n             <param name="outputs|plddts" value="true"/>\n             <param name="outputs|pae_csv" value="true"/>\n             <param name="outputs|model_pkls" value="true"/>\n             <param name="outputs|relax_json" value="true"/>\n+            <param name="outputs|timings_json" value="true"/>\n             <expand macro="test_output_plots_3" />\n             <expand macro="test_output_confidence_scores" />\n             <expand macro="test_output_plddts" />\n             <expand macro="test_output_pdb_models" />\n             <expand macro="test_output_pickles" />\n             <expand macro="test_output_relax_json" />\n+            <expand macro="test_output_timings_json" />\n             <expand macro="test_output_pae_csv" />\n         </test>\n     </tests>\n@@ -325,7 +383,7 @@\n \n     | AlphaFold v2: AI-guided 3D structural prediction of proteins\n     |\n-    | **NOTE: this tool packages AlphaFold v2.3.1.**\n+    | **NOTE: this tool packages** `a modified branch of AlphaFold v2.3.2. <https://github.com/neoformit/alphafold/tree/release_2.3.2_galaxy>`_\n     |\n     | This means that the neural network has been trained on PDBs with a release\n     | date before 2021-09-30 (the training cutoff was 2018-04-30 until ``v2.3.0``).\n@@ -333,12 +391,9 @@\n     | Find out more in the technical and release notes:\n     |\n \n-    - `Release notes for v2.3.1 <https://github.com/deepmind/alphafold/releases/tag/v2.3.1>`_\n+    - `Release notes for v2.3.2 <https://github.com/deepmind/alphafold/releases/tag/v2.3.2>`_\n     - `Technical notes for v2.3 <https://github.com/deepmind/alphafold/blob/main/docs/technical_note_v2.3.0.md>`_\n \n-    | If you want to use AlphaFold trained against an older cutoff date, switch to Galaxy version ``2.1.2`` (which was trained to data up to 2018-04-30).\n-    |\n-\n     **What it does**\n \n     *What is AlphaFold?*\n@@ -362,6 +417,7 @@\n     | You can choose to input either a file from your Galaxy history or paste a sequence into a text box.\n     | If you choose the ``multimer`` option, you can supply a FASTA file containing **multiple sequences** to be folded concurrently into a multimer.\n     |\n+    | For pairwise screening of target-candidate with multimer, you can submit a list of paired protein sequences in batch mode (i.e. two protein sequences in each FASTA file).\n     |\n \n     **Outputs**\n@@ -380,7 +436,7 @@\n \n     *PDB files*\n \n-    | Five PDB (Protein Data Bank) files are be created, ordered by rank, as predicted by AlphaFold.\n+    | PDB (Protein Data Bank) files (5 by default) are be created, ordered by rank, as predicted by AlphaFold. The tool produces 5 models by default, but this can be reduced with the "Limit model outputs" for a reduced run time.\n     | These files describe the molecular structures and can be used for downstream analysis. e.g. *in silico* molecular docking.\n     | **PLEASE NOTE** that all outputs have been renamed to their respective rank order, including model and model.pkl files.\n     |\n@@ -421,6 +477,12 @@\n     |\n     |\n \n+    *timings.json (optional)*\n+\n+    | A JSON-formatted text file containing the timings for each phase of the prediction.\n+    |\n+    |\n+\n     **AlphaFold configuration**\n \n     | We have configured AlphaFold to run with the parameters suggested by default on `AlphaFold\'s GitHub <https://github.com/deepmind/alphafold>`_.\n'
b
diff -r 2f7702fd0a4c -r 6ab1a261520a macro_output.xml
--- a/macro_output.xml Wed May 08 06:26:55 2024 +0000
+++ b/macro_output.xml Sun Jul 28 20:09:55 2024 +0000
[
@@ -1,9 +1,17 @@
 <macros>
     <xml name="output_pdb_models">
-        <data name="model5" format="pdb" from_work_dir="output/alphafold/ranked_4.pdb" label="${tool.name} on ${on_string}: PDB ranked 4"/>
-        <data name="model4" format="pdb" from_work_dir="output/alphafold/ranked_3.pdb" label="${tool.name} on ${on_string}: PDB ranked 3"/>
-        <data name="model3" format="pdb" from_work_dir="output/alphafold/ranked_2.pdb" label="${tool.name} on ${on_string}: PDB ranked 2"/>
-        <data name="model2" format="pdb" from_work_dir="output/alphafold/ranked_1.pdb" label="${tool.name} on ${on_string}: PDB ranked 1"/>
+        <data name="model5" format="pdb" from_work_dir="output/alphafold/ranked_4.pdb" label="${tool.name} on ${on_string}: PDB ranked 4">
+            <filter>advanced['limit_model_outputs'] > 4</filter>
+        </data>
+        <data name="model4" format="pdb" from_work_dir="output/alphafold/ranked_3.pdb" label="${tool.name} on ${on_string}: PDB ranked 3">
+            <filter>advanced['limit_model_outputs'] > 3</filter>
+        </data>
+        <data name="model3" format="pdb" from_work_dir="output/alphafold/ranked_2.pdb" label="${tool.name} on ${on_string}: PDB ranked 2">
+            <filter>advanced['limit_model_outputs'] > 2</filter>
+        </data>
+        <data name="model2" format="pdb" from_work_dir="output/alphafold/ranked_1.pdb" label="${tool.name} on ${on_string}: PDB ranked 1">
+            <filter>advanced['limit_model_outputs'] > 1</filter>
+        </data>
         <data name="model1" format="pdb" from_work_dir="output/alphafold/ranked_0.pdb" label="${tool.name} on ${on_string}: PDB ranked 0"/>
     </xml>
 
@@ -16,6 +24,7 @@
         >
             <filter>outputs['pae_csv']</filter>
             <filter>model_preset != "monomer"</filter>
+            <filter>advanced['limit_model_outputs'] > 4</filter>
         </data>
         <data
             name="pae_ranked_3"
@@ -25,6 +34,7 @@
         >
             <filter>outputs['pae_csv']</filter>
             <filter>model_preset != "monomer"</filter>
+            <filter>advanced['limit_model_outputs'] > 3</filter>
         </data>
         <data
             name="pae_ranked_2"
@@ -34,6 +44,7 @@
         >
             <filter>outputs['pae_csv']</filter>
             <filter>model_preset != "monomer"</filter>
+            <filter>advanced['limit_model_outputs'] > 2</filter>
         </data>
         <data
             name="pae_ranked_1"
@@ -43,6 +54,7 @@
         >
             <filter>outputs['pae_csv']</filter>
             <filter>model_preset != "monomer"</filter>
+            <filter>advanced['limit_model_outputs'] > 1</filter>
         </data>
         <data
             name="pae_ranked_0"
@@ -63,6 +75,7 @@
             label="${tool.name} on ${on_string}: ranked_4.pkl"
         >
             <filter>outputs['model_pkls']</filter>
+            <filter>advanced['limit_model_outputs'] > 4</filter>
         </data>
         <data
             name="output_ranked_3_pkl"
@@ -71,6 +84,7 @@
             label="${tool.name} on ${on_string}: ranked_3.pkl"
         >
             <filter>outputs['model_pkls']</filter>
+            <filter>advanced['limit_model_outputs'] > 3</filter>
         </data>
         <data
             name="output_ranked_2_pkl"
@@ -79,6 +93,7 @@
             label="${tool.name} on ${on_string}: ranked_2.pkl"
         >
             <filter>outputs['model_pkls']</filter>
+            <filter>advanced['limit_model_outputs'] > 2</filter>
         </data>
         <data
             name="output_ranked_1_pkl"
@@ -87,6 +102,7 @@
             label="${tool.name} on ${on_string}: ranked_1.pkl"
         >
             <filter>outputs['model_pkls']</filter>
+            <filter>advanced['limit_model_outputs'] > 1</filter>
         </data>
         <data
             name="output_ranked_0_pkl"
@@ -106,6 +122,7 @@
             label="${tool.name} on ${on_string}: pLDDT/PAE plot ranked 4"
         >
             <filter>outputs['plots']</filter>
+            <filter>advanced['limit_model_outputs'] > 4</filter>
         </data>
         <data
             name="plot_ranked_3"
@@ -114,6 +131,7 @@
             label="${tool.name} on ${on_string}: pLDDT/PAE plot ranked 3"
         >
             <filter>outputs['plots']</filter>
+            <filter>advanced['limit_model_outputs'] > 3</filter>
         </data>
         <data
             name="plot_ranked_2"
@@ -122,6 +140,7 @@
             label="${tool.name} on ${on_string}: pLDDT/PAE plot ranked 2"
         >
             <filter>outputs['plots']</filter>
+            <filter>advanced['limit_model_outputs'] > 2</filter>
         </data>
         <data
             name="plot_ranked_1"
@@ -130,6 +149,7 @@
             label="${tool.name} on ${on_string}: pLDDT/PAE plot ranked 1"
         >
             <filter>outputs['plots']</filter>
+            <filter>advanced['limit_model_outputs'] > 1</filter>
         </data>
         <data
             name="plot_ranked_0"
@@ -173,4 +193,15 @@
             <filter>outputs['relax_json']</filter>
         </data>
     </xml>
+
+    <xml name="output_timings_json">
+        <data
+            name="output_timings_json"
+            format="json"
+            from_work_dir="output/alphafold/timings.json"
+            label="${tool.name} on ${on_string}: timings.json"
+        >
+            <filter>outputs['timings_json']</filter>
+        </data>
+    </xml>
 </macros>
b
diff -r 2f7702fd0a4c -r 6ab1a261520a macro_test_output.xml
--- a/macro_test_output.xml Wed May 08 06:26:55 2024 +0000
+++ b/macro_test_output.xml Sun Jul 28 20:09:55 2024 +0000
b
@@ -26,6 +26,14 @@
         </output>
     </xml>
 
+    <xml name="test_output_timings_json">
+        <output name="output_timings_json">
+            <assert_contents>
+                <has_size min="500" />
+            </assert_contents>
+        </output>
+    </xml>
+
     <xml name="test_output_pdb_models">
         <output name="model1">
             <assert_contents>
b
diff -r 2f7702fd0a4c -r 6ab1a261520a scripts/alphafold.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/alphafold.html Sun Jul 28 20:09:55 2024 +0000
[
b'@@ -0,0 +1,656 @@\n+<!DOCTYPE html>\n+<html lang="en" dir="ltr">\n+\n+  <head>\n+    <meta charset="utf-8">\n+    <meta http-equiv="X-UA-Compatible" content="IE=edge">\n+    <meta name="viewport" content="width=device-width, initial-scale=1">\n+\n+    <title> Alphafold structure prediction </title>\n+\n+    <link rel="preconnect" href="https://fonts.googleapis.com">\n+    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n+    <link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap" rel="stylesheet">\n+    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">\n+    <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.0/chroma.min.js" integrity="sha512-yocoLferfPbcwpCMr8v/B0AB4SWpJlouBwgE0D3ZHaiP1nuu5djZclFEIj9znuqghaZ3tdCMRrreLoM8km+jIQ==" crossorigin="anonymous"></script>\n+\n+    <style type="text/css">\n+      * {\n+        margin: 0;\n+        padding: 0;\n+      }\n+      html, body {\n+        width: 100%;\n+        font-size: 1rem;\n+      }\n+      body {\n+        font-family: \'Ubuntu\', sans-serif;\n+      }\n+      canvas {\n+        background-color: white;\n+      }\n+      h1, h2, h3, h4, h5, h6 {\n+        color: dodgerblue;\n+        text-align: center;\n+        font-weight: lighter;\n+      }\n+      h1 {\n+        margin: 2rem;\n+        font-size: 3rem;\n+      }\n+      h2 {\n+        font-size: 2rem;\n+        margin-top: 1rem;\n+        margin-bottom: .5rem;\n+      }\n+      button.btn {\n+        color: #ccc;\n+        margin: 1rem;\n+        padding: .5rem;\n+        font-size: 1rem;\n+        min-width: 4rem;\n+        border: none;\n+        border-radius: .5rem;\n+        background-color: grey;\n+        transition-duration: 0.25s;\n+        cursor: pointer;\n+      }\n+      button.btn.selected {\n+        color: #eee;\n+        background-color: dodgerblue;\n+      }\n+      button.btn.green {\n+        color: #eee;\n+        background-color: #10941f;\n+      }\n+      button.btn:focus {\n+        outline: none;\n+        color: inherit;\n+      }\n+      button.btn:hover {\n+        color: white;\n+        box-shadow: 0 0 10px dodgerblue;\n+      }\n+      button.btn.green:hover {\n+        color: white;\n+        box-shadow: 0 0 10px limegreen;\n+      }\n+      .main {\n+        min-height: 90vh;\n+        position: relative;\n+      }\n+      .flex {\n+        display: flex;\n+        justify-content: center;\n+        align-items: center;\n+        padding: 1rem;\n+      }\n+      .col {\n+        flex-direction: column;\n+        flex-grow: 0;\n+      }\n+      .controls {\n+        padding-bottom: 10vh;\n+      }\n+      .box {\n+        padding: .5rem 1rem;\n+        margin: .5rem auto;\n+        width: fit-content;\n+        border-radius: 1rem;\n+      }\n+      .mono {\n+        font-family: monospace;\n+        color: #555;\n+        background-color: #ddd;\n+        padding: .25rem;\n+        border-radius: .25rem;\n+      }\n+      .space-1 {\n+        line-height: 1.2;\n+      }\n+      .space-2 {\n+        line-height: 1.5;\n+      }\n+      .relative {\n+        position: relative;\n+      }\n+      .legend {\n+        max-width: 350px;\n+      }\n+      .legend .scale {\n+        display: flex;\n+        flex-direction: column;\n+        align-items: center;\n+      }\n+      .legend .color {\n+        width: 150px;\n+        height: 30px;\n+        justify-content: space-between;\n+        background: linear-gradient(\n+          90deg,\n+          rgba(255,55,0,1)   0%,\n+          rgba(216,224,6,1)  33%,\n+          rgba(34,213,238,1) 66%,\n+          rgba(3,30,148,1)   100%\n+          );\n+      }\n+      .legend .ticks {\n+        margin-top: -1rem;\n+        width: 180px;\n+        justify-content: space-between;\n+      }\n+      #ngl-root-parent {\n+        width: 40vw;\n+        height: 30vw;\n+        margin: auto;\n+        position: relative;\n+      }\n+      #ngl-root {\n+        width: '..b' if (reps.length) {\n+        state.representations = {};\n+      } else {\n+        reps = [DEFAULT_REPRESENTATION];\n+      }\n+\n+      // Load PDB entry\n+      return stage.loadFile(uri(state.model)).then( (o) => {\n+        state.modelObject = o;\n+        reps.forEach( (r) => addModelRepresentation(r) );\n+        stage.setSpin(state.spin);\n+        o.autoView();\n+        setLoading(0);\n+      })\n+    }\n+\n+    // Representations ---------------------------------------------------------\n+\n+    const toggleModelRepresentation = (rep) => {\n+      rep in state.representations ?\n+        removeModelRepresentation(rep)\n+        : addModelRepresentation(rep)\n+    }\n+\n+    const addModelRepresentation = (rep) => {\n+      state.representations[rep] =\n+        state.modelObject.addRepresentation(rep, {colorScheme: COLORSCHEME});\n+      updateButtons();\n+    }\n+\n+    const removeModelRepresentation = (rep) => {\n+      o = state.representations[rep];\n+      state.modelObject.removeRepresentation(o);\n+      delete state.representations[rep];\n+      updateButtons();\n+    }\n+\n+    const clearModelRepresentations = () => {\n+      state.modelObject && state.modelObject.removeAllRepresentations();\n+      state.representations = {};\n+    }\n+\n+    // Actions -----------------------------------------------------------------\n+\n+    const toggleDark = () => {\n+      state.darkMode = !state.darkMode;\n+      stage.setParameters({\n+        backgroundColor: state.darkMode ? \'black\' : \'white\',\n+      });\n+      const btn = document.querySelector(\'#btn-toggle-dark\');\n+      btn && btn.classList.toggle(\'selected\');\n+    }\n+\n+    const setLoading = (state) => {\n+      document.getElementById(\'ngl-loading\')\n+        .style.display = state ? \'flex\' : \'none\';\n+      state.loading = state;\n+    }\n+\n+    const toggleSpin = () => {\n+      stage.toggleSpin();\n+      const btn = document.querySelector(\'#btn-toggle-spin\');\n+      btn && btn.classList.toggle(\'selected\');\n+      state.spin = !state.spin;\n+    }\n+\n+    const downloadPng = () => {\n+      const params = {\n+        factor: 3,\n+        antialias: true,\n+      }\n+      stage.makeImage(params).then( (blob) => {\n+        const name = MODELS[state.model].replace(\'.pdb\', \'.png\');\n+        const url = URL.createObjectURL(blob);\n+        makeDownload(url, name);\n+      })\n+    }\n+\n+    const downloadPdb = () => {\n+      const url = uri(state.model);\n+      const name = `alphafold_${MODELS[state.model]}`;\n+      makeDownload(url, name);\n+    }\n+\n+    const makeDownload = (url, name) => {\n+      // Will not work with cross-origin urls (i.e. during development)\n+      console.log(`Creating file download for ${name}, href ${url}`);\n+      const saveLink = document.createElement(\'a\');\n+      saveLink.href = url;\n+      saveLink.download = name;\n+      document.body.appendChild(saveLink);\n+      saveLink.dispatchEvent(\n+        new MouseEvent(\'click\', {\n+          bubbles: true,\n+          cancelable: true,\n+          view: window\n+        })\n+      );\n+      document.body.removeChild(saveLink);\n+    }\n+\n+    const updateButtons = () => {\n+      MODELS.forEach( (name, i) => {\n+        const id = `#btn-${name.replace(\'.pdb\', \'\')}`;\n+        const btn = document.querySelector(id);\n+        if (!btn) return\n+        i == state.model ?\n+          btn.classList.add(\'selected\')\n+          : btn.classList.remove(\'selected\');\n+      })\n+\n+      REPRESENTATIONS.forEach( (name) => {\n+        const id = `#btn-${name}`.replace(\'+\', \'-\');\n+        const btn = document.querySelector(id);\n+        if (!btn) return\n+        if (name in state.representations) {\n+          btn.classList.add(\'selected\')\n+        } else {\n+          btn.classList.remove(\'selected\');\n+        }\n+      });\n+\n+      // Show "Nothing to display" if no representations are selected\n+      document.querySelector(\'#ngl-nothing\').style.display =\n+        Object.keys(state.representations).length ?\n+        \'none\'\n+        : \'block\';\n+    }\n+\n+  </script>\n+\n+</html>\n'
b
diff -r 2f7702fd0a4c -r 6ab1a261520a scripts/outputs.py
--- a/scripts/outputs.py Wed May 08 06:26:55 2024 +0000
+++ b/scripts/outputs.py Sun Jul 28 20:09:55 2024 +0000
[
@@ -42,6 +42,12 @@
     'multimer': 'iptm+ptm',
 }
 
+HTML_PATH = Path(__file__).parent / "alphafold.html"
+HTML_OUTPUT_FILENAME = 'alphafold.html'
+HTML_BUTTON_ATTR = 'class="btn" id="btn-ranked_{rank}"'
+HTML_BUTTON_ATTR_DISABLED = (
+    'class="btn disabled" id="btn-ranked_{rank}" disabled')
+
 
 class Settings:
     """Parse and store settings/config."""
@@ -188,7 +194,7 @@
     """Write per-model confidence scores."""
     path = context.settings.workdir / OUTPUTS['model_confidence_scores']
     with open(path, 'w') as f:
-        for rank in range(1, 6):
+        for rank in range(1, len(context.model_pkl_paths) + 1):
             score = ranking.get_plddt_for_rank(rank)
             f.write(f'ranked_{rank - 1}\t{score:.2f}\n')
 
@@ -300,6 +306,22 @@
         plt.savefig(png_path)
 
 
+def template_html(context: ExecutionContext):
+    """Template HTML file.
+
+    Remove buttons that are redundant with limited model outputs.
+    """
+    print("Templating HTML file...")
+    with open(HTML_PATH) as f:
+        html = f.read()
+    for i in range(len(context.model_pkl_paths), 5):
+        btn_id = HTML_BUTTON_ATTR.format(rank=i)
+        btn_attr_disabled = HTML_BUTTON_ATTR_DISABLED.format(rank=i)
+        html = html.replace(btn_id, btn_attr_disabled)
+    with open(context.settings.output_dir / HTML_OUTPUT_FILENAME, 'w') as f:
+        f.write(html)
+
+
 def main():
     """Parse output files and generate additional output files."""
     settings = Settings()
@@ -307,6 +329,7 @@
     ranking = ResultRanking(context)
     write_confidence_scores(ranking, context)
     rekey_relax_metrics(ranking, context)
+    template_html(context)
 
     # Optional outputs
     if settings.output_model_pkls: