From 33c3eb5172dfc0af7c6d2c50540a0c03f54ee975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Mon, 7 Jul 2025 15:40:43 +0100 Subject: [PATCH 01/21] dnatco validation and restraints generation task --- core/CachedLookups.json | 78 +++- pipelines/dnatco_pipe/__init__.py | 0 pipelines/dnatco_pipe/script/__init__.py | 0 .../dnatco_pipe/script/dnatco_pipe.def.xml | 60 +++ .../script/dnatco_pipe.medline.txt | 90 +++++ pipelines/dnatco_pipe/script/dnatco_pipe.py | 90 +++++ .../dnatco_pipe/script/dnatco_pipe_gui.py | 76 ++++ .../dnatco_pipe/script/dnatco_pipe_report.py | 70 ++++ .../script/servalcat_pipe.def.xml | 74 ++-- .../servalcat_pipe/script/servalcat_pipe.py | 51 ++- .../script/servalcat_pipe_gui.py | 60 ++- .../script/servalcat_pipe_report.py | 20 +- qticons/CachedPixmapPaths.json | 2 +- qticons/dnatco_pipe.png | Bin 0 -> 6881 bytes qticons/dnatco_pipe_96.png | Bin 0 -> 12634 bytes wrappers/dnatco/__init__.py | 0 wrappers/dnatco/script/__init__.py | 0 wrappers/dnatco/script/dnatco.def.xml | 89 +++++ wrappers/dnatco/script/dnatco.medline.txt | 90 +++++ wrappers/dnatco/script/dnatco.py | 107 ++++++ wrappers/dnatco/script/dnatco_report.py | 349 ++++++++++++++++++ wrappers/servalcat/script/servalcat.def.xml | 10 + wrappers/servalcat/script/servalcat.py | 13 +- wrappers/servalcat/script/servalcat_report.py | 19 + 24 files changed, 1265 insertions(+), 83 deletions(-) create mode 100644 pipelines/dnatco_pipe/__init__.py create mode 100644 pipelines/dnatco_pipe/script/__init__.py create mode 100644 pipelines/dnatco_pipe/script/dnatco_pipe.def.xml create mode 100644 pipelines/dnatco_pipe/script/dnatco_pipe.medline.txt create mode 100644 pipelines/dnatco_pipe/script/dnatco_pipe.py create mode 100644 pipelines/dnatco_pipe/script/dnatco_pipe_gui.py create mode 100644 pipelines/dnatco_pipe/script/dnatco_pipe_report.py create mode 100644 qticons/dnatco_pipe.png create mode 100644 qticons/dnatco_pipe_96.png create mode 100644 wrappers/dnatco/__init__.py create mode 100644 wrappers/dnatco/script/__init__.py create mode 100644 wrappers/dnatco/script/dnatco.def.xml create mode 100644 wrappers/dnatco/script/dnatco.medline.txt create mode 100644 wrappers/dnatco/script/dnatco.py create mode 100644 wrappers/dnatco/script/dnatco_report.py diff --git a/core/CachedLookups.json b/core/CachedLookups.json index b35a9924c..090bcfc5f 100644 --- a/core/CachedLookups.json +++ b/core/CachedLookups.json @@ -141,6 +141,9 @@ "morda_i2", "phaser_singleMR" ], + "pipelines": [ + "dnatco_pipe" + ], "preferences": [ "guipreferences" ], @@ -157,6 +160,7 @@ "pdb_redo_api", "sheetbend", "zanuda", + "dnatco_pipe", "phaser_rnp_pipeline" ], "test": [ @@ -222,6 +226,7 @@ "buccaneer_mr", "cmapcoeff", "cpatterson", + "dnatco", "import_mosflm", "metalCoord", "prosmart", @@ -990,6 +995,28 @@ "modes": [] } }, + "dnatco": { + "0.0.0": { + "MAINTAINER": "Nobody", + "class": "wrappers.dnatco.script.dnatco_reportdnatco_report", + "clsModule": "wrappers.dnatco.script.dnatco_report", + "clsName": "dnatco_report", + "modes": [ + "Running" + ] + } + }, + "dnatco_pipe": { + "0.0.0": { + "MAINTAINER": "Nobody", + "class": "pipelines.dnatco_pipe.script.dnatco_pipe_reportdnatco_pipe_report", + "clsModule": "pipelines.dnatco_pipe.script.dnatco_pipe_report", + "clsName": "dnatco_pipe_report", + "modes": [ + "Running" + ] + } + }, "dr_mr_modelbuild_pipeline": { "0.0.0": { "MAINTAINER": "Nobody", @@ -2823,6 +2850,32 @@ "subTasks": [] } }, + "dnatco": { + "0.1": { + "DESCRIPTION": null, + "INTERRUPTLABEL": null, + "MAINTAINER": "martin.maly@mrc-lmb.cam.ac.uk", + "TASKTITLE": null, + "class": "wrappers.dnatco.script.dnatcodnatco", + "clsModule": "wrappers.dnatco.script.dnatco", + "clsName": "dnatco", + "internal": false, + "subTasks": [] + } + }, + "dnatco_pipe": { + "0.1": { + "DESCRIPTION": null, + "INTERRUPTLABEL": null, + "MAINTAINER": "martin.maly@mrc-lmb.cam.ac.uk", + "TASKTITLE": null, + "class": "pipelines.dnatco_pipe.script.dnatco_pipednatco_pipe", + "clsModule": "pipelines.dnatco_pipe.script.dnatco_pipe", + "clsName": "dnatco_pipe", + "internal": false, + "subTasks": [] + } + }, "dr_mr_modelbuild_pipeline": { "null": { "DESCRIPTION": null, @@ -3071,7 +3124,7 @@ } }, "metalCoord": { - "0.1": { + "0.2": { "DESCRIPTION": null, "INTERRUPTLABEL": null, "MAINTAINER": "martin.maly@mrc-lmb.cam.ac.uk", @@ -3833,7 +3886,7 @@ "DESCRIPTION": null, "INTERRUPTLABEL": null, "MAINTAINER": "martin.maly@mrc-lmb.cam.ac.uk", - "TASKTITLE": "Refinement against diffraction data or SPA map & optional restraints from ProSMART & MetalCoord", + "TASKTITLE": "Refinement against diffraction data or SPA map & optional restraints from ProSMART, MetalCoord and DNATCO", "class": "pipelines.servalcat_pipe.script.servalcat_pipeservalcat_pipe", "clsModule": "pipelines.servalcat_pipe.script.servalcat_pipe", "clsName": "servalcat_pipe", @@ -3841,7 +3894,8 @@ "subTasks": [ "servalcat", "prosmart", - "metalCoord" + "metalCoord", + "dnatco_pipe" ] } }, @@ -4173,6 +4227,7 @@ "coot_script_lines": "qticons/coot_script_lines.png", "coot_stepped_refine": "qticons/ccp4.png", "cpatterson": "qticons/cpatterson.png", + "cphasematch": "qticons/cphasematch.png", "crank2": "qticons/crank2.png", "crank2_comb_phdmmb": "qticons/ccp4.png", "crank2_dmfull": "qticons/ccp4.png", @@ -4194,6 +4249,7 @@ "developer_tools": "qticons/developer_tools.png", "dials_image": "qticons/dials_image.png", "dials_rlattice": "qticons/dials_rlattice.png", + "dnatco_pipe": "qticons/dnatco_pipe.png", "dr_mr_modelbuild_pipeline": "qticons/ccp4.png", "dui": "qticons/dui.png", "dummy": "qticons/ccp4.png", @@ -5491,6 +5547,20 @@ "taskVersion": 0.0 } }, + "dnatco_pipe": { + "0.0.0": { + "DESCRIPTION": "Restrain and validate nucleic acid structures", + "MAINTAINER": "Nobody", + "RANK": null, + "TASKTITLE": "DNATCO", + "class": "pipelines.dnatco_pipe.script.dnatco_pipe_guidnatco_pipe_gui", + "clsModule": "pipelines.dnatco_pipe.script.dnatco_pipe_gui", + "clsName": "dnatco_pipe_gui", + "shortTitle": "DNATCO", + "taskName": "dnatco_pipe", + "taskVersion": 0.1 + } + }, "dr_mr_modelbuild_pipeline": { "0.0.0": { "DESCRIPTION": "Data Reduction, MR, Model build pipeline", @@ -5754,7 +5824,7 @@ "clsName": "CmetalCoord_gui", "shortTitle": "MetalCoord", "taskName": "metalCoord", - "taskVersion": 1.0 + "taskVersion": 0.2 } }, "modelcraft": { diff --git a/pipelines/dnatco_pipe/__init__.py b/pipelines/dnatco_pipe/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pipelines/dnatco_pipe/script/__init__.py b/pipelines/dnatco_pipe/script/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml b/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml new file mode 100644 index 000000000..700489fb5 --- /dev/null +++ b/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml @@ -0,0 +1,60 @@ + + + + DEF + martinmaly + 13:15 27/Jun/25 + + 0.1 + dnatco_pipe + DNATCO - Restrain and validate nucleic acid structures + + + + + + + CCP4I2_TOP + wrappers/dnatco/script + dnatco.def.xml + + + + + + CPdbDataFile + + True + False + True + + + + CPdbDataFile + + True + True + + + + + + + CRefmacRestraintsDataFile + + + + + + + + CBoolean + + False + Compare with another structure model + + + + + + diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe.medline.txt b/pipelines/dnatco_pipe/script/dnatco_pipe.medline.txt new file mode 100644 index 000000000..891537ae4 --- /dev/null +++ b/pipelines/dnatco_pipe/script/dnatco_pipe.medline.txt @@ -0,0 +1,90 @@ +PMID- 32406923 +OWN - NLM +STAT- MEDLINE +DCOM- 20200908 +LR - 20240329 +IS - 1362-4962 (Electronic) +IS - 0305-1048 (Print) +IS - 0305-1048 (Linking) +VI - 48 +IP - 11 +DP - 2020 Jun 19 +TI - A unified dinucleotide alphabet describing both RNA and DNA structures. +PG - 6367-6381 +LID - 10.1093/nar/gkaa383 [doi] +AB - By analyzing almost 120 000 dinucleotides in over 2000 nonredundant nucleic acid + crystal structures, we define 96+1 diNucleotide Conformers, NtCs, which describe + the geometry of RNA and DNA dinucleotides. NtC classes are grouped into 15 codes + of the structural alphabet CANA (Conformational Alphabet of Nucleic Acids) to + simplify symbolic annotation of the prominent structural features of NAs and + their intuitive graphical display. The search for nontrivial patterns of NtCs + resulted in the identification of several types of RNA loops, some of them + observed for the first time. Over 30% of the nearly six million dinucleotides in + the PDB cannot be assigned to any NtC class but we demonstrate that up to a half + of them can be re-refined with the help of proper refinement targets. A + statistical analysis of the preferences of NtCs and CANA codes for the 16 + dinucleotide sequences showed that neither the NtC class AA00, which forms the + scaffold of RNA structures, nor BB00, the DNA most populated class, are sequence + neutral but their distributions are significantly biased. The reported automated + assignment of the NtC classes and CANA codes available at dnatco.org provides a + powerful tool for unbiased analysis of nucleic acid structures by structural and + molecular biologists. +CI - © The Author(s) 2020. Published by Oxford University Press on behalf of Nucleic + Acids Research. +FAU - Černý, Jiří +AU - Černý J +AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 + Vestec, Prague-West, Czech Republic. +FAU - Božíková, Paulína +AU - Božíková P +AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 + Vestec, Prague-West, Czech Republic. +FAU - Svoboda, Jakub +AU - Svoboda J +AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 + Vestec, Prague-West, Czech Republic. +FAU - Schneider, Bohdan +AU - Schneider B +AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 + Vestec, Prague-West, Czech Republic. +LA - eng +PT - Journal Article +PT - Research Support, Non-U.S. Gov't +PL - England +TA - Nucleic Acids Res +JT - Nucleic acids research +JID - 0411011 +RN - 0 (Nucleotides) +RN - 0 (RNA, Catalytic) +RN - 0 (Riboswitch) +RN - 63231-63-0 (RNA) +RN - 9007-49-2 (DNA) +SB - IM +MH - Binding Sites +MH - Biocatalysis +MH - DNA/*chemistry/*classification +MH - *Nucleic Acid Conformation +MH - *Nucleotide Motifs +MH - Nucleotides/*chemistry/*classification +MH - RNA/*chemistry/*classification +MH - RNA, Catalytic/chemistry/metabolism +MH - Reproducibility of Results +MH - Ribosomes/chemistry/metabolism +MH - Riboswitch +PMC - PMC7293047 +EDAT- 2020/05/15 06:00 +MHDA- 2020/09/09 06:00 +PMCR- 2020/05/14 +CRDT- 2020/05/15 06:00 +PHST- 2020/04/30 00:00 [accepted] +PHST- 2020/04/11 00:00 [revised] +PHST- 2020/04/11 00:00 [received] +PHST- 2020/05/15 06:00 [pubmed] +PHST- 2020/09/09 06:00 [medline] +PHST- 2020/05/15 06:00 [entrez] +PHST- 2020/05/14 00:00 [pmc-release] +AID - 5837055 [pii] +AID - gkaa383 [pii] +AID - 10.1093/nar/gkaa383 [doi] +PST - ppublish +SO - Nucleic Acids Res. 2020 Jun 19;48(11):6367-6381. doi: 10.1093/nar/gkaa383. diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe.py b/pipelines/dnatco_pipe/script/dnatco_pipe.py new file mode 100644 index 000000000..bdf56e466 --- /dev/null +++ b/pipelines/dnatco_pipe/script/dnatco_pipe.py @@ -0,0 +1,90 @@ +""" + dnatco_pipe.py: CCP4 GUI Project + Copyright (C) 2025 MRC-LMB + Author: Martin Maly + + This library is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + version 3, modified in accordance with the provisions of the + license to address the requirements of UK law. + + You should have received a copy of the modified GNU Lesser General + Public License along with this library. If not, copies may be + downloaded from http://www.ccp4.ac.uk/ccp4license.php + + 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 Lesser General Public License for more details. +""" + +import os +import shutil +import xml.etree.ElementTree as ET +from PySide2 import QtCore +from core.CCP4PluginScript import CPluginScript +from core.CCP4ErrorHandling import * +from core import CCP4Utils + + +class dnatco_pipe(CPluginScript): + + TASKMODULE = 'pipelines' # Where this plugin will appear on gui + TASKNAME = 'dnatco_pipe' # Task name - should be same as class name + TASKVERSION = 0.1 # Version of this plugin + MAINTAINER = 'martin.maly@mrc-lmb.cam.ac.uk' + + ERROR_CODES = { 201 : { 'description' : 'No output restraint file from DNATCO' }, + 202 : { 'description' : 'No output extended mmCIF file from DNATCO' }, + 203 : { 'description' : 'No output PDF report file from DNATCO' }, + 204 : { 'description' : 'Failed to dnatcoify structure' }, + } + + + def process(self): + self.dnatco1 = self.dnatcoCreate(self.container.inputData.XYZIN1) + self.dnatco1.process() + if ( + bool(self.container.controlParameters.TOGGLE_XYZIN2) + and os.path.isfile(str(self.container.inputData.XYZIN2.fullPath)) + ): + self.dnatco2 = self.dnatcoCreate(self.container.inputData.XYZIN2) + self.dnatco2.process() + self.process_finish() + + + def dnatcoCreate(self, model): + dnatco = self.makePluginObject('dnatco') + dnatco.container.inputData.XYZIN.set(model) + dnatco.container.controlParameters.copyData(self.container.controlParameters) + # dnatco.container.controlParameters.GENERATE_RESTRAINTS.set(self.container.controlParameters.GENERATE_RESTRAINTS) + # dnatco.container.controlParameters.MAX_RMSD.set(self.container.controlParameters.MAX_RMSD) + # dnatco.container.controlParameters.RESTRAINTS_SIGMA.set(self.container.controlParameters.RESTRAINTS_SIGMA) + self.connectSignal(dnatco, 'finished', self.dnatcoFinished) + dnatco.waitForFinished = -1 + return dnatco + + + @QtCore.Slot(dict) + def dnatcoFinished(self, statusDict): + status = statusDict['finishStatus'] + if status == CPluginScript.FAILED: + self.reportStatus(status) + + + def process_finish(self): + xmlText = "" + xmlroot = ET.fromstringlist(["", xmlText, ""]) + ET.indent(xmlroot, space="\t", level=0) + with open(self.makeFileName('PROGRAMXML'), 'w', encoding="utf-8") as programXML: + CCP4Utils.writeXML(programXML, ET.tostring(xmlroot)) + + if bool(self.container.controlParameters.GENERATE_RESTRAINTS): + dnatco_obj = self.dnatco2 if bool(self.container.controlParameters.TOGGLE_XYZIN2) else self.dnatco1 + self.container.outputData.RESTRAINTS.annotation.set(dnatco_obj.container.outputData.RESTRAINTS.annotation) + restraintsPathJob = str(dnatco_obj.container.outputData.RESTRAINTS.fullPath) + restraintsPathPipeline = str(self.container.outputData.RESTRAINTS.fullPath) + if os.path.isfile(restraintsPathJob): + shutil.copyfile(restraintsPathJob, restraintsPathPipeline) + + self.reportStatus(CPluginScript.SUCCEEDED) diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe_gui.py b/pipelines/dnatco_pipe/script/dnatco_pipe_gui.py new file mode 100644 index 000000000..dd7dc5725 --- /dev/null +++ b/pipelines/dnatco_pipe/script/dnatco_pipe_gui.py @@ -0,0 +1,76 @@ +""" + dnatco_pipe_gui.py: CCP4 GUI Project + Copyright (C) 2025 MRC-LMB + Author: Martin Maly + + This library is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + version 3, modified in accordance with the provisions of the + license to address the requirements of UK law. + + You should have received a copy of the modified GNU Lesser General + Public License along with this library. If not, copies may be + downloaded from http://www.ccp4.ac.uk/ccp4license.php + + 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 Lesser General Public License for more details. +""" + +from qtgui.CCP4TaskWidget import CTaskWidget + +#------------------------------------------------------------------- +class dnatco_pipe_gui(CTaskWidget): +#------------------------------------------------------------------- + + TASKNAME = 'dnatco_pipe' + TASKVERSION = 0.1 + TASKMODULE = ['refinement'] + TASKTITLE = 'DNATCO' + SHORTTASKTITLE = 'DNATCO' + DESCRIPTION = 'Restrain and validate nucleic acid structures' + WHATNEXT = ['servalcat_pipe'] + + def __init__(self,parent): + CTaskWidget.__init__(self,parent) + + def drawContents(self): + + # self.setProgramHelpFile('dnatco_pipe') + + self.openFolder(folderFunction="inputData") + + self.openSubFrame(frame=[True], title="Input data") + self.autoGenerate( + self.container.inputData, + selection={"includeParameters": ["XYZIN1"]}, + ) + self.openSubFrame(frame=[True], toggle=['controlParameters.TOGGLE_XYZIN2', 'open', [False]]) + self.autoGenerate( + self.container.controlParameters, + selection={"includeParameters": ["GENERATE_RESTRAINTS"]}, + ) + self.closeSubFrame() + self.closeSubFrame() + + self.createLine(['widget', 'TOGGLE_XYZIN2', 'label', 'Compare with another structure model']) + self.openSubFrame(frame=[True], toggle=['controlParameters.TOGGLE_XYZIN2', 'open', [True]]) + self.autoGenerate( + self.container.inputData, + selection={"includeParameters": ["XYZIN2"]}, + ) + self.autoGenerate( + self.container.controlParameters, + selection={"includeParameters": ["GENERATE_RESTRAINTS"]}, + ) + self.closeSubFrame() + + self.openSubFrame(frame=[True], title="Parameters for restraints generation", toggle=['controlParameters.GENERATE_RESTRAINTS', 'open', [True]]) + self.autoGenerate( + self.container.controlParameters, + selection={"includeParameters": ["MAX_RMSD", "RESTRAINTS_SIGMA"]}, + ) + self.closeSubFrame() + self.closeFolder() + diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe_report.py b/pipelines/dnatco_pipe/script/dnatco_pipe_report.py new file mode 100644 index 000000000..c11dee704 --- /dev/null +++ b/pipelines/dnatco_pipe/script/dnatco_pipe_report.py @@ -0,0 +1,70 @@ +""" + dnatco_pipe_report.py: CCP4 GUI Project + Copyright (C) 2025 MRC-LMB + Author: Martin Maly + + This library is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + version 3, modified in accordance with the provisions of the + license to address the requirements of UK law. + + You should have received a copy of the modified GNU Lesser General + Public License along with this library. If not, copies may be + downloaded from http://www.ccp4.ac.uk/ccp4license.php + + 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 Lesser General Public License for more details. +""" + +from report.CCP4ReportParser import * +from core import CCP4Modules +from wrappers.dnatco.script.dnatco_report import dnatco_report +import os + + +class dnatco_pipe_report(Report): + TASKNAME= 'dnatco_pipe' + RUNNING = True + + def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): + Report.__init__(self, xmlnode=xmlnode, jobInfo=jobInfo, jobStatus=jobStatus, **kw) + + if jobStatus is None or jobStatus.lower() == 'nooutput': return + + projectid = self.jobInfo.get("projectid", None) + jobNumber = self.jobInfo.get("jobnumber", None) + jobId = self.jobInfo.get("jobid", None) + jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId=jobId) + self.jobLog = os.path.join(jobDirectory, "log.txt") + if jobStatus is not None and jobStatus.lower() == "running": + self.runningReport(parent=self) + else: + self.defaultReport(parent=self) + + + def runningReport(self, parent=None): + if parent is None: + parent = self + if os.path.isfile(self.jobLog): + jobLogFold = parent.addFold(label="DNATCO log", initiallyOpen=True) + jobLogFold.addPre("DNATCO is running...") + + + def defaultReport(self, parent=None): + if parent is None: + parent = self + self.addDiv(style="clear:both;") # gives space for the title + + jobId = self.jobInfo.get("jobid", None) + jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId) + + jobDirectory1 = os.path.join(jobDirectory, "job_1") # TODO: derive from jobId? + jobDirectory2 = os.path.join(jobDirectory, "job_2") # TODO: derive from jobId? + jobDirectories = [jobDirectory1] + if os.path.isdir(jobDirectory2): + jobDirectories.append(jobDirectory2) + + dnatco_report1 = dnatco_report() + dnatco_report1.defaultReport(parent, jobDirectories) \ No newline at end of file diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml b/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml index 395f370c2..0232ad4b1 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml +++ b/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml @@ -460,76 +460,46 @@ - - - CString - - False - True - DISABLED,SELECTED,UNSELECTED,NONUCLEICACID,RIGIDMODE - DISABLED - - - + + CBoolean False - - - CString - - False - True - ALL,MANUAL - all,specify restraint types - ALL - - - - CBoolean - - False - - - + + CBoolean False - - - CString - - # Replace this with optional additional libg keywords - - - - - - - CBoolean - - False - CString False True - ZN,NA_MG - Zinc,Zinc/Sodium/Magnesium - ZN + DISABLED,SELECTED,UNSELECTED,NONUCLEICACID,RIGIDMODE + DISABLED - - CBoolean - - True - + + CFloat + + 0.5 + Maximum allowed NtC RMSD (in angstroem) + Maximum allowed NtC RMSD (in angstroem) + + + + CFloat + + 1.0 + Restraints sigma factor + Restraints sigma factor + + CBoolean diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe.py b/pipelines/servalcat_pipe/script/servalcat_pipe.py index 398eb35a2..ed504905f 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe.py +++ b/pipelines/servalcat_pipe/script/servalcat_pipe.py @@ -34,7 +34,7 @@ class servalcat_pipe(CPluginScript): TASKMODULE = 'refinement' SHORTTASKTITLE = 'Servalcat' - TASKTITLE = 'Refinement against diffraction data or SPA map & optional restraints from ProSMART & MetalCoord' + TASKTITLE = 'Refinement against diffraction data or SPA map & optional restraints from ProSMART, MetalCoord and DNATCO' TASKNAME = 'servalcat_pipe' # Task name - same as class name MAINTAINER = 'martin.maly@mrc-lmb.cam.ac.uk' TASKVERSION= 0.1 @@ -43,7 +43,7 @@ class servalcat_pipe(CPluginScript): TIMEOUT_PERIOD = 240 MAXNJOBS = 4 PERFORMANCECLASS = 'CServalcatPerformance' - SUBTASKS=['servalcat','prosmart','metalCoord'] + SUBTASKS=['servalcat','prosmart','metalCoord','dnatco_pipe'] RUNEXTERNALPROCESS=False PURGESEARCHLIST = [[ 'refmac%*/hklout.mtz', 0, "hklout" ], [ 'refmac%*/hklout.mtz', 7, "hklout" ], [ '*%*/ANOMFPHIOUT.mtz', 1, "ANOMFPHIOUT" ], [ '*%*/DIFANOMFPHIOUT.mtz', 1, "DIFANOMFPHIOUT" ]] @@ -66,6 +66,12 @@ def startProcess(self, processId): sys.stderr.write("ERROR while running ProSMART: " + str(e) + "\n") self.reportStatus(CPluginScript.FAILED) return CPluginScript.FAILED + try: + self.executeDnatcoRestraints() + except Exception as e: + sys.stderr.write("ERROR while running DNATCO restraints generation: " + str(e) + "\n") + self.reportStatus(CPluginScript.FAILED) + return CPluginScript.FAILED try: self.executeMetalCoords() except Exception as e: @@ -135,6 +141,44 @@ def prosmartNucleicAcidFinished(self, statusDict): if status == CPluginScript.FAILED: self.reportStatus(status) + def executeDnatcoRestraints(self): + # DNATCO job for restraints generation before refinement + if bool(self.container.dnatco.TOGGLE_RESTRAINTS): + self.dnatco = self.makePluginObject('dnatco') + self.dnatco.container.inputData.XYZIN.set(self.container.inputData.XYZIN) + self.dnatco.container.controlParameters.GENERATE_RESTRAINTS.set(True) + self.dnatco.container.controlParameters.MAX_RMSD.set(self.container.dnatco.MAX_RMSD) + self.dnatco.container.controlParameters.RESTRAINTS_SIGMA.set(self.container.dnatco.RESTRAINTS_SIGMA) + self.connectSignal(self.dnatco, 'finished', self.dnatcoRestraintsFinished) + self.dnatco.waitForFinished = -1 + self.dnatco.process() + + @QtCore.Slot(dict) + def dnatcoRestraintsFinished(self, statusDict): + status = statusDict['finishStatus'] + if status == CPluginScript.FAILED: + self.reportStatus(status) + + def executeDnatcoValidation(self): + # DNATCO job for structure validation - structure models before and after refinement + # Needs to be fixed + return + if bool(self.container.dnatco.TOGGLE_VALIDATION): + self.dnatcoValidation = self.makePluginObject('dnatco_pipe') + self.dnatcoValidation.container.inputData.XYZIN1.set(self.container.inputData.XYZIN) + self.dnatcoValidation.container.inputData.XYZIN2.set(self.container.outputData.CIFFILE) + self.dnatcoValidation.container.controlParameters.GENERATE_RESTRAINTS.set(False) + self.dnatcoValidation.container.controlParameters.TOGGLE_XYZIN2.set(True) + self.connectSignal(self.dnatcoValidation, 'finished', self.dnatcoValidationFinished) + self.dnatcoValidation.waitForFinished = -1 + self.dnatcoValidation.process() + + @QtCore.Slot(dict) + def dnatcoValidationFinished(self, statusDict): + status = statusDict['finishStatus'] + if status == CPluginScript.FAILED: + self.reportStatus(status) + def executeMetalCoords(self): if self.container.metalCoordPipeline.RUN_METALCOORD and \ self.container.metalCoordPipeline.GENERATE_OR_USE == "GENERATE": @@ -292,6 +336,8 @@ def createServalcatJob(self, withWeight=-1, inputCoordinates=None, ncyc=-1): # else report error? else: result.container.inputData.METALCOORD_RESTRAINTS=self.container.metalCoordPipeline.METALCOORD_RESTRAINTS + if self.container.dnatco.TOGGLE_RESTRAINTS: + result.container.inputData.DNATCO_RESTRAINTS=self.dnatco.container.outputData.RESTRAINTS if self.container.prosmartProtein.TOGGLE: result.container.controlParameters.PROSMART_PROTEIN_SGMN=self.container.prosmartProtein.SGMN result.container.controlParameters.PROSMART_PROTEIN_SGMX=self.container.prosmartProtein.SGMX @@ -796,6 +842,7 @@ def finishUp(self, servalcatJob): cleanup.purgeJob(self.servalcatPostCootPlugin.jobId,context="extended_intermediate",reportMode="skip") self.multimericValidation() + self.executeDnatcoValidation() if self.container.controlParameters.RUN_ADP_ANALYSIS: self.adp_analysis( str(self.container.outputData.CIFFILE.fullPath), diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py b/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py index 107de48fb..049f9c31c 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py +++ b/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py @@ -17,12 +17,6 @@ GNU Lesser General Public License for more details. """ -""" - Andrey Lebedev September 2011 - refmac_martin gui - Liz Potterton Aug 2012 - convert for MTZ ADO's demo - Liz Potterton Oct 2012 - Moved mini-MTZ version to refmac_martin -""" - from PySide2 import QtWidgets,QtCore from qtgui import CCP4TaskWidget from core import CCP4XtalData @@ -171,7 +165,7 @@ def drawContents(self): self.drawAdvanced() # small change introduced to allow for automatically loading a keyword file in the 'advanced' tab self.setProsmartProteinMode() self.setProsmartNucleicAcidMode() - self.setLibgMode() + self.setDnatcoMode() return def twinHelpPressed(self): @@ -319,6 +313,36 @@ def drawRestraints( self ): self.createLine( [ 'label', 'Not available.' ] ) self.closeSubFrame() + self.createLine( [ 'subtitle', 'DNATCO External Restraints for Nucleic Acids'] ) + if self.isEditable(): + self.container.dnatco.TOGGLE_RESTRAINTS.dataChanged.connect(self.setDnatcoMode) + self.container.controlParameters.REFINEMENT_MODE.dataChanged.connect(self.setDnatcoMode) + self.container.controlParameters.UNRESTRAINED.dataChanged.connect(self.setDnatcoMode) + self.container.controlParameters.FIX_XYZ.dataChanged.connect(self.setDnatcoMode) + self.container.controlParameters.JELLY_ONLY.dataChanged.connect(self.setDnatcoMode) + self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'DISABLED' ] ] ) + self.createLine( [ 'label', 'Specify atomic model before setting up external restraints' ] ) + self.closeSubFrame() + self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'NONUCLEICACID' ] ] ) + self.createLine( [ 'label', 'Input atomic model contains no nucleotide chains' ] ) + self.closeSubFrame() + self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'RIGIDMODE' ] ] ) + self.createLine( [ 'label', 'Not available in Rigid Body mode.' ] ) + self.closeSubFrame() + self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'NOTUSED' ] ] ) + self.createLine( [ 'label', 'Not available.' ] ) + self.closeSubFrame() + self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'UNSELECTED' ] ] ) + self.createLine( [ 'widget', 'dnatco.TOGGLE_RESTRAINTS', 'label', 'Generate and apply restraints for nucleic acids (experimental)' ] ) + self.closeSubFrame() + self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'SELECTED' ] ] ) + self.createLine( [ 'widget', 'dnatco.TOGGLE_RESTRAINTS', 'label', 'Generate and apply restraints for nucleic acids (experimental):' ] ) + self.autoGenerate( + self.container.dnatco, + selection={"includeParameters": ["MAX_RMSD", "RESTRAINTS_SIGMA"]}, + ) + self.closeSubFrame() + self.createLine( [ 'subtitle', 'ProSMART External Restraints for Protein Chains'] ) if self.isEditable(): self.container.prosmartProtein.TOGGLE.dataChanged.connect(self.setProsmartProteinMode) @@ -419,9 +443,6 @@ def hideProsmartProteinBfac(self): def hideProsmartNucleicAcidBfac(self): return self.container.prosmartNucleicAcid.ADVANCED and not self.container.prosmartNucleicAcid.TOGGLE_BFAC - - def showLibgOptions(self): - return str(self.container.libg.OPTION) == 'MANUAL' def drawAdvanced( self ): indent = '      ' @@ -460,6 +481,7 @@ def drawAdvanced( self ): self.createLine( [ 'widget', 'VALIDATE_IRIS', 'label', 'Generate Iris validation report' ] ) self.createLine( [ 'widget', 'VALIDATE_RAMACHANDRAN', 'label', 'Generate Ramachandran plots' ] ) self.createLine( [ 'widget', 'VALIDATE_MOLPROBITY', 'label', 'Run MolProbity to analyse geometry' ] ) + self.createLine( [ 'widget', 'dnatco.TOGGLE_VALIDATION', 'label', 'Run DNATCO to validate nucleic acids' ] ) self.createLine( [ 'widget', 'RUN_ADP_ANALYSIS', 'label', 'Run ADP analysis' ] ) self.createLine( [ 'label', 'Atoms with a B-value lower than the first quartile - factor * interquartile_range
or higher than the third quartile + factor * interquartile_range to be reported. Factor:', @@ -520,7 +542,7 @@ def modelChanged(self): self.updateViewFromModel() self.setProsmartProteinMode() self.setProsmartNucleicAcidMode() - self.setLibgMode() + self.setDnatcoMode() self.getMonomersWithMetals() @QtCore.Slot() @@ -572,21 +594,23 @@ def setProsmartNucleicAcidMode(self): self.validate() @QtCore.Slot() - def setLibgMode(self): + def setDnatcoMode(self): if self.ToggleRigidModeOn(): - self.container.libg.MODE.set('RIGIDMODE') + self.container.dnatco.MODE.set('RIGIDMODE') elif self.container.inputData.XYZIN.isSet(): if self.container.inputData.NUCLEOTIDE_CHAINS.isSet(): if self.container.inputData.NUCLEOTIDE_CHAINS: - if self.container.libg.TOGGLE: - self.container.libg.MODE.set('SELECTED') + if self.container.dnatco.TOGGLE_RESTRAINTS: + self.container.dnatco.MODE.set('SELECTED') else: - self.container.libg.MODE.set('UNSELECTED') + self.container.dnatco.MODE.set('UNSELECTED') + self.container.dnatco.TOGGLE_VALIDATION.set(True) self.validate() return - self.container.libg.MODE.set('NONUCLEICACID') + self.container.dnatco.MODE.set('NONUCLEICACID') + self.container.dnatco.TOGGLE_VALIDATION.set(False) else: - self.container.libg.MODE.set('DISABLED') + self.container.dnatco.MODE.set('DISABLED') self.validate() def getMonomersWithMetals(self): diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe_report.py b/pipelines/servalcat_pipe/script/servalcat_pipe_report.py index 1c8401a5a..57ffe4de0 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe_report.py +++ b/pipelines/servalcat_pipe/script/servalcat_pipe_report.py @@ -1,10 +1,28 @@ +""" + servalcat_pipe_report.py: CCP4 GUI Project + Copyright (C) 2024 University of Southampton, MRC LMB Cambridge + + This library is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + version 3, modified in accordance with the provisions of the + license to address the requirements of UK law. + + You should have received a copy of the modified GNU Lesser General + Public License along with this library. If not, copies may be + downloaded from http://www.ccp4.ac.uk/ccp4license.php + + 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 Lesser General Public License for more details. +""" + import sys from xml.etree import ElementTree as ET from report.CCP4ReportParser import * from wrappers.servalcat.script import servalcat_report from wrappers.validate_protein.script import validate_protein_report -import base64 class servalcat_pipe_report(Report): diff --git a/qticons/CachedPixmapPaths.json b/qticons/CachedPixmapPaths.json index d8cb3a24f..2b3a93f50 100644 --- a/qticons/CachedPixmapPaths.json +++ b/qticons/CachedPixmapPaths.json @@ -1 +1 @@ -{"RefmacKeywordFile": "RefmacKeywordFile.png", "Mol2DataFile_96": "Mol2DataFile_96.png", "Annotation": "Annotation.png", "zanuda": "zanuda.png", "thought": "thought.png", "refinement": "refinement.png", "MakeProjectsAndDoLigandPipeline": "MakeProjectsAndDoLigandPipeline.png", "molrep_pipe": "molrep_pipe.png", "arcimboldo": "arcimboldo.png", "acorn": "acorn.png", "LidiaAcedrg_96": "LidiaAcedrg_96.png", "forward": "forward.png", "tab-close-highlight": "tab-close-highlight.png", "book_96": "book_96.png", "arcimboldo_96": "arcimboldo_96.png", "phaser_singleMR_96": "phaser_singleMR_96.png", "UnmergedDataFile": "UnmergedDataFile.png", "molrep_selfrot_96": "molrep_selfrot_96.png", "freerflag": "freerflag.png", "AsuDataFile": "AsuDataFile.png", "fileopen": "fileopen.png", "gesamt": "gesamt.png", "DictDataFile": "DictDataFile.png", "phaser_EP_AUTO": "phaser_EP_AUTO.png", "simple_xia2_96": "simple_xia2_96.png", "list_add": "list_add.png", "sculptor_96": "sculptor_96.png", "import_mosflm_96": "import_mosflm_96.png", "file_manager2": "file_manager2.png", "privateer_96": "privateer_96.png", "chainsaw_96": "chainsaw_96.png", "list_delete_grey": "list_delete_grey.png", "phaser_pipeline": "phaser_pipeline.png", "AlternativeImportXIA2_96": "AlternativeImportXIA2_96.png", "aimless_pipe": "aimless_pipe.png", "showlogfile_96": "showlogfile_96.png", "buccaneer_build_refine_mr": "buccaneer_build_refine_mr.png", "help-no-highlight": "help-no-highlight.png", "molrep_den": "molrep_den.png", "ccp4mg_edit_model_96": "ccp4mg_edit_model_96.png", "DataFile": "DataFile.png", "import_processed": "import_processed.png", "bullet_arrow_right": "bullet_arrow_right.png", "showlogfile": "showlogfile.png", "view_forward_96": "view_forward_96.png", "aimless_pipe_96": "aimless_pipe_96.png", "FreeRDataFile": "FreeRDataFile.png", "MapCoeffsDataFile": "MapCoeffsDataFile.png", "expt_data_utility_96": "expt_data_utility_96.png", "shelx": "shelx.png", "editbfac": "editbfac.png", "job_interrupted": "job_interrupted.png", "xia2_xds_96": "xia2_xds_96.png", "green-tick": "green-tick.png", "coot_rsr_morph": "coot_rsr_morph.png", "ProvideAlignment": "ProvideAlignment.png", "prosmart_refmac_96": "prosmart_refmac_96.png", "xia2_dials_96": "xia2_dials_96.png", "phaser_EP_LLG_96": "phaser_EP_LLG_96.png", "AMPLE": "AMPLE.png", "csymmatch_96": "csymmatch_96.png", "i2Dimple": "i2Dimple.png", "blank": "blank.png", "coot_script_lines_96": "coot_script_lines_96.png", "arp_warp_classic": "arp_warp_classic.png", "biblio": "biblio.png", "phaser_ensembler_96": "phaser_ensembler_96.png", "showi2run": "showi2run.png", "undo_96": "undo_96.png", "import_arrow_new_96": "import_arrow_new_96.png", "ProvideAsuContents_96": "ProvideAsuContents_96.png", "fragon": "fragon.png", "AlternativeImportXIA2": "AlternativeImportXIA2.png", "list_delete_red": "list_delete_red.png", "undone": "undone.png", "ShelxCECompareHands": "ShelxCECompareHands.png", "developer_tools": "developer_tools.png", "gesamt_96": "gesamt_96.png", "pointless_reindexToMatch_96": "pointless_reindexToMatch_96.png", "chltofom_96": "chltofom_96.png", "alpha_fold": "alpha_fold.png", "expt_phasing": "expt_phasing.png", "error_star": "error_star.png", "cphasematch": "cphasematch.png", "phaser_ensembler": "phaser_ensembler.png", "parrot": "parrot.png", "scaleit": "scaleit.png", "shelx_96": "shelx_96.png", "MakeMonster": "MakeMonster.png", "editbfac_96": "editbfac_96.png", "export_arrow_new": "export_arrow_new.png", "database": "database.png", "splitMtz": "splitMtz.png", "data_entry": "data_entry.png", "data_processing": "data_processing.png", "ProvideSequence_96": "ProvideSequence_96.png", "ShelxCD": "ShelxCD.png", "phaser_pipeline_96": "phaser_pipeline_96.png", "data_reduction_96": "data_reduction_96.png", "nautilus_build_refine": "nautilus_build_refine.png", "ProvideTLS_96": "ProvideTLS_96.png", "ccp4i2": "ccp4i2.png", "search": "search.png", "PrepareDeposit_96": "PrepareDeposit_96.png", "ShelxCE": "ShelxCE.png", "listLine": "listLine.png", "dui_96": "dui_96.png", "up_96": "up_96.png", "pdb_redo_api": "pdb_redo_api.png", "download_96": "download_96.png", "pause": "pause.png", "expt_data_utility": "expt_data_utility.png", "dials_image_96": "dials_image_96.png", "help-highlight": "help-highlight.png", "developer_tools_96": "developer_tools_96.png", "acorn_96": "acorn_96.png", "back": "back.png", "xia2_multiplex": "xia2_multiplex.png", "ShowList": "ShowList.png", "CootHistoryDataFile": "CootHistoryDataFile.png", "tableone_96": "tableone_96.png", "newtab_96": "newtab_96.png", "gears": "gears.png", "ProvideAlignment_96": "ProvideAlignment_96.png", "buster": "buster.png", "importRefnData": "importRefnData.png", "greenarrowsup": "greenarrowsup.png", "imosflm_96": "imosflm_96.png", "data_reduction": "data_reduction.png", "cphasematch_96": "cphasematch_96.png", "zoom_96": "zoom_96.png", "LidiaAcedrgNew": "LidiaAcedrgNew.png", "MTZDataFile_96": "MTZDataFile_96.png", "export": "export.png", "phaser_rnp_pipeline": "phaser_rnp_pipeline.png", "list_delete": "list_delete.png", "clone_96": "clone_96.png", "lorestr_i2": "lorestr_i2.png", "molrep_den_96": "molrep_den_96.png", "cmapcoeff": "cmapcoeff.png", "list_add_grey": "list_add_grey.png", "backgroundJobTitle": "backgroundJobTitle.png", "PhaserSolDataFile": "PhaserSolDataFile.png", "down_96": "down_96.png", "file_96": "file_96.png", "validate_protein": "validate_protein.png", "buccaneer_build_refine_EP": "buccaneer_build_refine_EP.png", "i2Dimple_96": "i2Dimple_96.png", "dustbin": "dustbin.png", "morda_i2": "morda_i2.png", "density_modification_96": "density_modification_96.png", "phaser_MR_AUTO": "phaser_MR_AUTO.png", "cpatterson": "cpatterson.png", "MakeLink": "MakeLink.png", "freerflag_96": "freerflag_96.png", "ccp4mg_edit_model": "ccp4mg_edit_model.png", "xia2_xds": "xia2_xds.png", "greenarrowsupshadow": "greenarrowsupshadow.png", "modelcraft": "modelcraft.png", "file_info": "file_info.png", "dui": "dui.png", "PhaserRFileDataFile": "PhaserRFileDataFile.png", "book": "book.png", "import_merged_96": "import_merged_96.png", "List": "List.png", "refln_data_analysis": "refln_data_analysis.png", "model_building": "model_building.png", "import_mosflm": "import_mosflm.png", "xia2_ssx_reduce_96": "xia2_ssx_reduce_96.png", "importRefnData_96": "importRefnData_96.png", "refinement_96": "refinement_96.png", "back_96": "back_96.png", "phaser_MR_AUTO_96": "phaser_MR_AUTO_96.png", "shelxeMR_96": "shelxeMR_96.png", "coordinate_selector_96": "coordinate_selector_96.png", "pairef_96": "pairef_96.png", "matthews": "matthews.png", "shelxeMR": "shelxeMR.png", "SIMBAD": "SIMBAD.png", "ProvideTLS": "ProvideTLS.png", "validation_96": "validation_96.png", "molecular_replacement": "molecular_replacement.png", "view_forward": "view_forward.png", "servalcat_pipe": "servalcat_pipe.png", "xia2_ssx_reduce": "xia2_ssx_reduce.png", "MakeLink_96": "MakeLink_96.png", "coot_script_lines": "coot_script_lines.png", "mrparse_96": "mrparse_96.png", "zanuda_96": "zanuda_96.png", "findmyseq": "findmyseq.png", "SIMBAD_96": "SIMBAD_96.png", "dials_image": "dials_image.png", "adding_stats_to_mmcif_i2": "adding_stats_to_mmcif_i2.png", "MakeProjectsAndDoLigandPipeline_96": "MakeProjectsAndDoLigandPipeline_96.png", "import_xia2": "import_xia2.png", "ObsDataFile": "ObsDataFile.png", "ProvideAsuContents": "ProvideAsuContents.png", "greendot": "greendot.png", "fileopen_96": "fileopen_96.png", "phaser_rnp_pipeline_96": "phaser_rnp_pipeline_96.png", "matthews_96": "matthews_96.png", "servalcat_pipe_96": "servalcat_pipe_96.png", "tab-close": "tab-close.png", "dials_rlattice": "dials_rlattice.png", "simple_xia2": "simple_xia2.png", "pairef": "pairef.png", "project": "project.png", "model_data_utility_96": "model_data_utility_96.png", "list_add_red": "list_add_red.png", "molecular_replacement_96": "molecular_replacement_96.png", "convert2mtz": "convert2mtz.png", "toc-plus": "toc-plus.png", "question": "question.png", "splitMtz_96": "splitMtz_96.png", "import_processed_96": "import_processed_96.png", "ctruncate": "ctruncate.png", "aimless": "aimless.png", "reload_96": "reload_96.png", "help-highlight_96": "help-highlight_96.png", "List_grey": "List_grey.png", "ctruncate_96": "ctruncate_96.png", "sad": "sad.png", "ProvideSequence": "ProvideSequence.png", "xia2_dials": "xia2_dials.png", "mergeMtz": "mergeMtz.png", "model_data_utility": "model_data_utility.png", "lorestr_i2_96": "lorestr_i2_96.png", "pisapipe_96": "pisapipe_96.png", "density_modification": "density_modification.png", "toc-minus": "toc-minus.png", "red-cross": "red-cross.png", "edstats": "edstats.png", "LidiaAcedrgNew_96": "LidiaAcedrgNew_96.png", "model_building_96": "model_building_96.png", "phaser_simple_96": "phaser_simple_96.png", "import_xia2_96": "import_xia2_96.png", "phaser_EP_AUTO_96": "phaser_EP_AUTO_96.png", "pisapipe": "pisapipe.png", "youtube": "youtube.png", "mrbump_basic": "mrbump_basic.png", "redarrowright": "redarrowright.png", "csymmatch": "csymmatch.png", "crank2": "crank2.png", "bioinformatics_96": "bioinformatics_96.png", "ccp4mg_edit_nomrbump": "ccp4mg_edit_nomrbump.png", "ResidueRange": "ResidueRange.png", "zoom": "zoom.png", "phaser_EP_LLG": "phaser_EP_LLG.png", "add_fractional_coords": "add_fractional_coords.png", "mergeMtz_96": "mergeMtz_96.png", "SyncToDjango_96": "SyncToDjango_96.png", "SeqAlignDataFile": "SeqAlignDataFile.png", "AUSPEX": "AUSPEX.png", "convert2mtz_96": "convert2mtz_96.png", "running": "running.png", "nautilus": "nautilus.png", "dials_rlattice_96": "dials_rlattice_96.png", "new_project_96": "new_project_96.png", "coot_find_waters": "coot_find_waters.png", "ShelxCE_96": "ShelxCE_96.png", "EnsemblePdbDataFile": "EnsemblePdbDataFile.png", "SubstituteLigand_96": "SubstituteLigand_96.png", "bioinformatics": "bioinformatics.png", "ShelxCD_96": "ShelxCD_96.png", "bullet_arrow_down": "bullet_arrow_down.png", "file_manager": "file_manager.png", "undo": "undo.png", "rosette": "rosette.png", "buster_96": "buster_96.png", "edstats_96": "edstats_96.png", "taskmenu_96": "taskmenu_96.png", "AMPLE_96": "AMPLE_96.png", "clone": "clone.png", "data_entry_96": "data_entry_96.png", "molrep_pipe_96": "molrep_pipe_96.png", "PrepareDeposit": "PrepareDeposit.png", "SeqDataFile": "SeqDataFile.png", "ccp4": "ccp4.png", "phaser_singleMR": "phaser_singleMR.png", "molrep_selfrot": "molrep_selfrot.png", "slicendice_96": "slicendice_96.png", "export_arrow_new_96": "export_arrow_new_96.png", "clustalw": "clustalw.png", "coot_find_waters_96": "coot_find_waters_96.png", "MakeMonster_96": "MakeMonster_96.png", "coot_rebuild": "coot_rebuild.png", "ccp4_96": "ccp4_96.png", "import_merged": "import_merged.png", "up": "up.png", "SceneDataFile": "SceneDataFile.png", "help_96": "help_96.png", "PhsDataFile": "PhsDataFile.png", "gears2": "gears2.png", "pdbview_edit": "pdbview_edit.png", "xia2_multiplex_96": "xia2_multiplex_96.png", "download": "download.png", "newtab": "newtab.png", "expt_phasing_96": "expt_phasing_96.png", "search-plus": "search-plus.png", "AsuDataFile_96": "AsuDataFile_96.png", "ShelxCECompareHands_96": "ShelxCECompareHands_96.png", "ligands": "ligands.png", "aimless_96": "aimless_96.png", "parrot_96": "parrot_96.png", "phaser_simple": "phaser_simple.png", "ccp4mg_edit_nomrbump_96": "ccp4mg_edit_nomrbump_96.png", "unsatisfactory": "unsatisfactory.png", "imosflm": "imosflm.png", "chainsaw": "chainsaw.png", "coot_rebuild_96": "coot_rebuild_96.png", "TestObsConversions": "TestObsConversions.png", "running_dark": "running_dark.png", "density_calculator": "density_calculator.png", "down": "down.png", "TestObsConversions_96": "TestObsConversions_96.png", "phaser_EP": "phaser_EP.png", "PdbDataFile": "PdbDataFile.png", "taskmenu": "taskmenu.png", "adding_stats_to_mmcif_i2_96": "adding_stats_to_mmcif_i2_96.png", "validation": "validation.png", "privateer": "privateer.png", "mrparse": "mrparse.png", "validate_protein_96": "validate_protein_96.png", "qtpisa_96": "qtpisa_96.png", "buccaneer_build_refine_mr_96": "buccaneer_build_refine_mr_96.png", "coordinate_selector": "coordinate_selector.png", "SubstituteLigand": "SubstituteLigand.png", "job": "job.png", "prosmart_refmac": "prosmart_refmac.png", "sculptor": "sculptor.png", "help-no-highlight_96": "help-no-highlight_96.png", "biblio_96": "biblio_96.png", "import_arrow_new": "import_arrow_new.png", "cell": "cell.png", "ligands_96": "ligands_96.png", "findmyseq_96": "findmyseq_96.png", "export_96": "export_96.png", "crank2_96": "crank2_96.png", "morda_i2_96": "morda_i2_96.png", "buccaneer_build_refine_EP_96": "buccaneer_build_refine_EP_96.png", "MapDataFile": "MapDataFile.png", "SyncToDjango": "SyncToDjango.png", "qtpisa": "qtpisa.png", "MTZDataFile": "MTZDataFile.png", "refln_data_analysis_96": "refln_data_analysis_96.png", "help": "help.png", "ccp4mg_general": "ccp4mg_general.png", "forward_96": "forward_96.png", "cmapcoeff_96": "cmapcoeff_96.png", "LidiaAcedrg": "LidiaAcedrg.png", "new_project": "new_project.png", "redarrowrightshadow": "redarrowrightshadow.png", "MDLMolDataFile": "MDLMolDataFile.png", "reload": "reload.png", "file": "file.png", "MiniMtzDataFile": "MiniMtzDataFile.png", "tableone": "tableone.png", "MDLMolDataFile_96": "MDLMolDataFile_96.png", "slicendice": "slicendice.png", "pdbview_edit_96": "pdbview_edit_96.png", "chltofom": "chltofom.png", "data_processing_96": "data_processing_96.png", "Mol2DataFile": "Mol2DataFile.png", "ccp4mg_general_96": "ccp4mg_general_96.png", "import_arrow": "import_arrow.png", "pointless_reindexToMatch": "pointless_reindexToMatch.png", "fragon_96": "fragon_96.png", "clustalw_96": "clustalw_96.png"} \ No newline at end of file +{"density_calculator": "density_calculator.png", "TestObsConversions_96": "TestObsConversions_96.png", "SeqDataFile": "SeqDataFile.png", "aimless": "aimless.png", "bullet_arrow_down": "bullet_arrow_down.png", "PrepareDeposit": "PrepareDeposit.png", "pisapipe": "pisapipe.png", "MakeProjectsAndDoLigandPipeline": "MakeProjectsAndDoLigandPipeline.png", "import_xia2": "import_xia2.png", "acorn": "acorn.png", "project": "project.png", "add_fractional_coords": "add_fractional_coords.png", "help": "help.png", "density_modification_96": "density_modification_96.png", "refinement": "refinement.png", "LidiaAcedrgNew_96": "LidiaAcedrgNew_96.png", "clone": "clone.png", "validate_protein_96": "validate_protein_96.png", "ShelxCECompareHands": "ShelxCECompareHands.png", "ccp4mg_edit_model": "ccp4mg_edit_model.png", "molrep_den": "molrep_den.png", "buster": "buster.png", "file": "file.png", "model_data_utility_96": "model_data_utility_96.png", "xia2_ssx_reduce": "xia2_ssx_reduce.png", "fileopen_96": "fileopen_96.png", "help-highlight_96": "help-highlight_96.png", "new_project": "new_project.png", "book_96": "book_96.png", "phaser_pipeline_96": "phaser_pipeline_96.png", "aimless_pipe_96": "aimless_pipe_96.png", "mrparse": "mrparse.png", "toc-plus": "toc-plus.png", "validate_protein": "validate_protein.png", "newtab_96": "newtab_96.png", "ProvideSequence": "ProvideSequence.png", "ccp4i2": "ccp4i2.png", "mergeMtz": "mergeMtz.png", "undo_96": "undo_96.png", "FreeRDataFile": "FreeRDataFile.png", "back": "back.png", "parrot_96": "parrot_96.png", "List_grey": "List_grey.png", "coot_script_lines_96": "coot_script_lines_96.png", "phaser_simple": "phaser_simple.png", "pisapipe_96": "pisapipe_96.png", "density_modification": "density_modification.png", "developer_tools": "developer_tools.png", "lorestr_i2": "lorestr_i2.png", "redarrowrightshadow": "redarrowrightshadow.png", "import_mosflm": "import_mosflm.png", "MakeLink_96": "MakeLink_96.png", "xia2_dials_96": "xia2_dials_96.png", "export_arrow_new": "export_arrow_new.png", "PdbDataFile": "PdbDataFile.png", "ccp4mg_edit_nomrbump_96": "ccp4mg_edit_nomrbump_96.png", "rosette": "rosette.png", "tab-close-highlight": "tab-close-highlight.png", "molrep_pipe_96": "molrep_pipe_96.png", "UnmergedDataFile": "UnmergedDataFile.png", "cphasematch_96": "cphasematch_96.png", "ShelxCE_96": "ShelxCE_96.png", "SyncToDjango": "SyncToDjango.png", "expt_data_utility": "expt_data_utility.png", "SIMBAD_96": "SIMBAD_96.png", "crank2": "crank2.png", "molrep_selfrot_96": "molrep_selfrot_96.png", "convert2mtz_96": "convert2mtz_96.png", "shelx_96": "shelx_96.png", "xia2_dials": "xia2_dials.png", "refinement_96": "refinement_96.png", "phaser_singleMR_96": "phaser_singleMR_96.png", "toc-minus": "toc-minus.png", "greenarrowsupshadow": "greenarrowsupshadow.png", "error_star": "error_star.png", "morda_i2": "morda_i2.png", "aimless_pipe": "aimless_pipe.png", "list_add_grey": "list_add_grey.png", "slicendice_96": "slicendice_96.png", "xia2_ssx_reduce_96": "xia2_ssx_reduce_96.png", "green-tick": "green-tick.png", "phaser_ensembler": "phaser_ensembler.png", "List": "List.png", "model_building": "model_building.png", "search": "search.png", "import_xia2_96": "import_xia2_96.png", "list_delete": "list_delete.png", "MTZDataFile": "MTZDataFile.png", "ResidueRange": "ResidueRange.png", "import_arrow": "import_arrow.png", "pause": "pause.png", "list_add": "list_add.png", "ShelxCE": "ShelxCE.png", "chltofom": "chltofom.png", "greenarrowsup": "greenarrowsup.png", "ccp4mg_edit_model_96": "ccp4mg_edit_model_96.png", "PrepareDeposit_96": "PrepareDeposit_96.png", "undo": "undo.png", "prosmart_refmac_96": "prosmart_refmac_96.png", "SceneDataFile": "SceneDataFile.png", "phaser_rnp_pipeline": "phaser_rnp_pipeline.png", "database": "database.png", "chltofom_96": "chltofom_96.png", "data_processing": "data_processing.png", "download": "download.png", "phaser_ensembler_96": "phaser_ensembler_96.png", "MapCoeffsDataFile": "MapCoeffsDataFile.png", "freerflag": "freerflag.png", "morda_i2_96": "morda_i2_96.png", "coordinate_selector": "coordinate_selector.png", "editbfac_96": "editbfac_96.png", "lorestr_i2_96": "lorestr_i2_96.png", "sculptor_96": "sculptor_96.png", "AlternativeImportXIA2": "AlternativeImportXIA2.png", "buccaneer_build_refine_mr_96": "buccaneer_build_refine_mr_96.png", "zanuda_96": "zanuda_96.png", "view_forward_96": "view_forward_96.png", "import_merged_96": "import_merged_96.png", "phaser_EP": "phaser_EP.png", "download_96": "download_96.png", "newtab": "newtab.png", "phaser_EP_AUTO_96": "phaser_EP_AUTO_96.png", "cphasematch": "cphasematch.png", "developer_tools_96": "developer_tools_96.png", "book": "book.png", "phaser_MR_AUTO": "phaser_MR_AUTO.png", "data_reduction": "data_reduction.png", "bullet_arrow_right": "bullet_arrow_right.png", "export": "export.png", "csymmatch_96": "csymmatch_96.png", "Mol2DataFile": "Mol2DataFile.png", "adding_stats_to_mmcif_i2": "adding_stats_to_mmcif_i2.png", "importRefnData_96": "importRefnData_96.png", "fragon": "fragon.png", "cell": "cell.png", "alpha_fold": "alpha_fold.png", "reload": "reload.png", "ProvideAsuContents": "ProvideAsuContents.png", "ccp4mg_general": "ccp4mg_general.png", "phaser_singleMR": "phaser_singleMR.png", "help-no-highlight_96": "help-no-highlight_96.png", "forward": "forward.png", "unsatisfactory": "unsatisfactory.png", "showi2run": "showi2run.png", "ctruncate_96": "ctruncate_96.png", "AsuDataFile_96": "AsuDataFile_96.png", "help_96": "help_96.png", "MiniMtzDataFile": "MiniMtzDataFile.png", "coordinate_selector_96": "coordinate_selector_96.png", "coot_find_waters": "coot_find_waters.png", "MakeMonster_96": "MakeMonster_96.png", "list_delete_grey": "list_delete_grey.png", "ctruncate": "ctruncate.png", "coot_rsr_morph": "coot_rsr_morph.png", "molrep_den_96": "molrep_den_96.png", "i2Dimple_96": "i2Dimple_96.png", "edstats_96": "edstats_96.png", "ProvideAlignment": "ProvideAlignment.png", "phaser_simple_96": "phaser_simple_96.png", "qtpisa": "qtpisa.png", "buccaneer_build_refine_EP": "buccaneer_build_refine_EP.png", "ObsDataFile": "ObsDataFile.png", "file_manager": "file_manager.png", "splitMtz": "splitMtz.png", "imosflm_96": "imosflm_96.png", "simple_xia2_96": "simple_xia2_96.png", "fragon_96": "fragon_96.png", "phaser_pipeline": "phaser_pipeline.png", "greendot": "greendot.png", "biblio": "biblio.png", "taskmenu": "taskmenu.png", "gears": "gears.png", "dials_rlattice": "dials_rlattice.png", "DataFile": "DataFile.png", "arp_warp_classic": "arp_warp_classic.png", "phaser_EP_LLG_96": "phaser_EP_LLG_96.png", "validation_96": "validation_96.png", "shelx": "shelx.png", "aimless_96": "aimless_96.png", "youtube": "youtube.png", "expt_phasing": "expt_phasing.png", "tab-close": "tab-close.png", "servalcat_pipe_96": "servalcat_pipe_96.png", "cmapcoeff": "cmapcoeff.png", "phaser_EP_AUTO": "phaser_EP_AUTO.png", "imosflm": "imosflm.png", "tableone_96": "tableone_96.png", "clustalw": "clustalw.png", "import_arrow_new": "import_arrow_new.png", "findmyseq_96": "findmyseq_96.png", "ProvideTLS_96": "ProvideTLS_96.png", "xia2_xds": "xia2_xds.png", "DictDataFile": "DictDataFile.png", "servalcat_pipe": "servalcat_pipe.png", "xia2_xds_96": "xia2_xds_96.png", "expt_phasing_96": "expt_phasing_96.png", "coot1_96": "coot1_96.png", "pdbview_edit": "pdbview_edit.png", "data_entry_96": "data_entry_96.png", "dui_96": "dui_96.png", "export_arrow_new_96": "export_arrow_new_96.png", "molecular_replacement": "molecular_replacement.png", "PhaserRFileDataFile": "PhaserRFileDataFile.png", "EnsemblePdbDataFile": "EnsemblePdbDataFile.png", "new_project_96": "new_project_96.png", "import_processed": "import_processed.png", "biblio_96": "biblio_96.png", "dials_image": "dials_image.png", "edstats": "edstats.png", "question": "question.png", "model_building_96": "model_building_96.png", "refln_data_analysis": "refln_data_analysis.png", "pointless_reindexToMatch": "pointless_reindexToMatch.png", "thought": "thought.png", "SIMBAD": "SIMBAD.png", "ShelxCD_96": "ShelxCD_96.png", "modelcraft": "modelcraft.png", "clustalw_96": "clustalw_96.png", "crank2_96": "crank2_96.png", "list_add_red": "list_add_red.png", "file_manager2": "file_manager2.png", "dnatco_pipe_96": "dnatco_pipe_96.png", "refln_data_analysis_96": "refln_data_analysis_96.png", "MakeMonster": "MakeMonster.png", "showlogfile_96": "showlogfile_96.png", "nautilus_build_refine": "nautilus_build_refine.png", "RefmacKeywordFile": "RefmacKeywordFile.png", "shelxeMR_96": "shelxeMR_96.png", "buster_96": "buster_96.png", "model_data_utility": "model_data_utility.png", "gesamt": "gesamt.png", "sculptor": "sculptor.png", "xia2_multiplex": "xia2_multiplex.png", "running_dark": "running_dark.png", "acorn_96": "acorn_96.png", "i2Dimple": "i2Dimple.png", "phaser_EP_LLG": "phaser_EP_LLG.png", "AUSPEX": "AUSPEX.png", "arcimboldo_96": "arcimboldo_96.png", "ccp4_96": "ccp4_96.png", "listLine": "listLine.png", "validation": "validation.png", "xia2_multiplex_96": "xia2_multiplex_96.png", "nautilus": "nautilus.png", "LidiaAcedrg_96": "LidiaAcedrg_96.png", "zanuda": "zanuda.png", "coot1": "coot1.png", "view_forward": "view_forward.png", "AMPLE": "AMPLE.png", "running": "running.png", "adding_stats_to_mmcif_i2_96": "adding_stats_to_mmcif_i2_96.png", "pairef_96": "pairef_96.png", "forward_96": "forward_96.png", "AMPLE_96": "AMPLE_96.png", "ccp4mg_edit_nomrbump": "ccp4mg_edit_nomrbump.png", "ProvideAlignment_96": "ProvideAlignment_96.png", "TestObsConversions": "TestObsConversions.png", "red-cross": "red-cross.png", "file_96": "file_96.png", "molecular_replacement_96": "molecular_replacement_96.png", "dnatco_pipe": "dnatco_pipe.png", "up_96": "up_96.png", "SeqAlignDataFile": "SeqAlignDataFile.png", "export_96": "export_96.png", "privateer": "privateer.png", "ProvideAsuContents_96": "ProvideAsuContents_96.png", "fileopen": "fileopen.png", "editbfac": "editbfac.png", "molrep_pipe": "molrep_pipe.png", "expt_data_utility_96": "expt_data_utility_96.png", "buccaneer_build_refine_EP_96": "buccaneer_build_refine_EP_96.png", "bioinformatics_96": "bioinformatics_96.png", "cmapcoeff_96": "cmapcoeff_96.png", "dui": "dui.png", "blank": "blank.png", "Annotation": "Annotation.png", "CootHistoryDataFile": "CootHistoryDataFile.png", "data_reduction_96": "data_reduction_96.png", "MakeLink": "MakeLink.png", "Mol2DataFile_96": "Mol2DataFile_96.png", "privateer_96": "privateer_96.png", "mrparse_96": "mrparse_96.png", "down_96": "down_96.png", "job": "job.png", "help-highlight": "help-highlight.png", "ligands": "ligands.png", "pdbview_edit_96": "pdbview_edit_96.png", "prosmart_refmac": "prosmart_refmac.png", "mrbump_basic": "mrbump_basic.png", "qtpisa_96": "qtpisa_96.png", "ccp4": "ccp4.png", "cpatterson": "cpatterson.png", "import_processed_96": "import_processed_96.png", "taskmenu_96": "taskmenu_96.png", "pointless_reindexToMatch_96": "pointless_reindexToMatch_96.png", "sad": "sad.png", "shelxeMR": "shelxeMR.png", "mergeMtz_96": "mergeMtz_96.png", "data_processing_96": "data_processing_96.png", "pdb_redo_api": "pdb_redo_api.png", "matthews_96": "matthews_96.png", "coot_script_lines": "coot_script_lines.png", "up": "up.png", "MakeProjectsAndDoLigandPipeline_96": "MakeProjectsAndDoLigandPipeline_96.png", "file_info": "file_info.png", "MDLMolDataFile": "MDLMolDataFile.png", "search-plus": "search-plus.png", "tableone": "tableone.png", "dials_rlattice_96": "dials_rlattice_96.png", "redarrowright": "redarrowright.png", "ProvideTLS": "ProvideTLS.png", "SubstituteLigand": "SubstituteLigand.png", "buccaneer_build_refine_mr": "buccaneer_build_refine_mr.png", "backgroundJobTitle": "backgroundJobTitle.png", "ShelxCD": "ShelxCD.png", "freerflag_96": "freerflag_96.png", "MTZDataFile_96": "MTZDataFile_96.png", "help-no-highlight": "help-no-highlight.png", "importRefnData": "importRefnData.png", "AlternativeImportXIA2_96": "AlternativeImportXIA2_96.png", "LidiaAcedrg": "LidiaAcedrg.png", "coot_rebuild_96": "coot_rebuild_96.png", "coot_find_waters_96": "coot_find_waters_96.png", "gesamt_96": "gesamt_96.png", "reload_96": "reload_96.png", "bioinformatics": "bioinformatics.png", "chainsaw": "chainsaw.png", "down": "down.png", "MapDataFile": "MapDataFile.png", "zoom": "zoom.png", "slicendice": "slicendice.png", "gears2": "gears2.png", "phaser_rnp_pipeline_96": "phaser_rnp_pipeline_96.png", "showlogfile": "showlogfile.png", "dustbin": "dustbin.png", "import_mosflm_96": "import_mosflm_96.png", "SubstituteLigand_96": "SubstituteLigand_96.png", "zoom_96": "zoom_96.png", "pairef": "pairef.png", "arcimboldo": "arcimboldo.png", "back_96": "back_96.png", "ShelxCECompareHands_96": "ShelxCECompareHands_96.png", "AsuDataFile": "AsuDataFile.png", "PhaserSolDataFile": "PhaserSolDataFile.png", "SyncToDjango_96": "SyncToDjango_96.png", "clone_96": "clone_96.png", "splitMtz_96": "splitMtz_96.png", "import_arrow_new_96": "import_arrow_new_96.png", "phaser_MR_AUTO_96": "phaser_MR_AUTO_96.png", "simple_xia2": "simple_xia2.png", "dials_image_96": "dials_image_96.png", "coot_rebuild": "coot_rebuild.png", "import_merged": "import_merged.png", "MDLMolDataFile_96": "MDLMolDataFile_96.png", "PhsDataFile": "PhsDataFile.png", "scaleit": "scaleit.png", "data_entry": "data_entry.png", "molrep_selfrot": "molrep_selfrot.png", "list_delete_red": "list_delete_red.png", "ligands_96": "ligands_96.png", "parrot": "parrot.png", "LidiaAcedrgNew": "LidiaAcedrgNew.png", "ProvideSequence_96": "ProvideSequence_96.png", "job_interrupted": "job_interrupted.png", "ccp4mg_general_96": "ccp4mg_general_96.png", "findmyseq": "findmyseq.png", "undone": "undone.png", "ShowList": "ShowList.png", "matthews": "matthews.png", "csymmatch": "csymmatch.png", "convert2mtz": "convert2mtz.png", "chainsaw_96": "chainsaw_96.png"} \ No newline at end of file diff --git a/qticons/dnatco_pipe.png b/qticons/dnatco_pipe.png new file mode 100644 index 0000000000000000000000000000000000000000..4dfe74f150c2ecd7fc67ad95925f37349007b6d0 GIT binary patch literal 6881 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 literal 0 HcmV?d00001 diff --git a/qticons/dnatco_pipe_96.png b/qticons/dnatco_pipe_96.png new file mode 100644 index 0000000000000000000000000000000000000000..bb94b40cb1bbe5eaa806ee2a5be1b94d99014040 GIT binary patch literal 12634 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%0AehEO9e6 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 literal 0 HcmV?d00001 diff --git a/wrappers/dnatco/__init__.py b/wrappers/dnatco/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wrappers/dnatco/script/__init__.py b/wrappers/dnatco/script/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wrappers/dnatco/script/dnatco.def.xml b/wrappers/dnatco/script/dnatco.def.xml new file mode 100644 index 000000000..92b6f8d7d --- /dev/null +++ b/wrappers/dnatco/script/dnatco.def.xml @@ -0,0 +1,89 @@ + + + + DEF + martinmaly + 13:15 27/Jun/25 + + 0.1 + dnatco + DNATCO - Restrain and validate nucleic acid structures + + + + + + + CPdbDataFile + + True + False + True + + + + + + + + CRefmacRestraintsDataFile + + + + + CPdbDataFile + + + 1 + 2 + + False + + + + CPDFDataFile + + + DNATCO report (PDF) + + False + + + + + + + CFloat + + 0.5 + Maximum allowed NtC RMSD (in angstroem) + Maximum allowed NtC RMSD (in angstroem) + + + + CFloat + + 1.0 + Restraints sigma factor + Restraints sigma factor + + + + CBoolean + + False + Generate restraints + + + + + + diff --git a/wrappers/dnatco/script/dnatco.medline.txt b/wrappers/dnatco/script/dnatco.medline.txt new file mode 100644 index 000000000..891537ae4 --- /dev/null +++ b/wrappers/dnatco/script/dnatco.medline.txt @@ -0,0 +1,90 @@ +PMID- 32406923 +OWN - NLM +STAT- MEDLINE +DCOM- 20200908 +LR - 20240329 +IS - 1362-4962 (Electronic) +IS - 0305-1048 (Print) +IS - 0305-1048 (Linking) +VI - 48 +IP - 11 +DP - 2020 Jun 19 +TI - A unified dinucleotide alphabet describing both RNA and DNA structures. +PG - 6367-6381 +LID - 10.1093/nar/gkaa383 [doi] +AB - By analyzing almost 120 000 dinucleotides in over 2000 nonredundant nucleic acid + crystal structures, we define 96+1 diNucleotide Conformers, NtCs, which describe + the geometry of RNA and DNA dinucleotides. NtC classes are grouped into 15 codes + of the structural alphabet CANA (Conformational Alphabet of Nucleic Acids) to + simplify symbolic annotation of the prominent structural features of NAs and + their intuitive graphical display. The search for nontrivial patterns of NtCs + resulted in the identification of several types of RNA loops, some of them + observed for the first time. Over 30% of the nearly six million dinucleotides in + the PDB cannot be assigned to any NtC class but we demonstrate that up to a half + of them can be re-refined with the help of proper refinement targets. A + statistical analysis of the preferences of NtCs and CANA codes for the 16 + dinucleotide sequences showed that neither the NtC class AA00, which forms the + scaffold of RNA structures, nor BB00, the DNA most populated class, are sequence + neutral but their distributions are significantly biased. The reported automated + assignment of the NtC classes and CANA codes available at dnatco.org provides a + powerful tool for unbiased analysis of nucleic acid structures by structural and + molecular biologists. +CI - © The Author(s) 2020. Published by Oxford University Press on behalf of Nucleic + Acids Research. +FAU - Černý, Jiří +AU - Černý J +AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 + Vestec, Prague-West, Czech Republic. +FAU - Božíková, Paulína +AU - Božíková P +AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 + Vestec, Prague-West, Czech Republic. +FAU - Svoboda, Jakub +AU - Svoboda J +AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 + Vestec, Prague-West, Czech Republic. +FAU - Schneider, Bohdan +AU - Schneider B +AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 + Vestec, Prague-West, Czech Republic. +LA - eng +PT - Journal Article +PT - Research Support, Non-U.S. Gov't +PL - England +TA - Nucleic Acids Res +JT - Nucleic acids research +JID - 0411011 +RN - 0 (Nucleotides) +RN - 0 (RNA, Catalytic) +RN - 0 (Riboswitch) +RN - 63231-63-0 (RNA) +RN - 9007-49-2 (DNA) +SB - IM +MH - Binding Sites +MH - Biocatalysis +MH - DNA/*chemistry/*classification +MH - *Nucleic Acid Conformation +MH - *Nucleotide Motifs +MH - Nucleotides/*chemistry/*classification +MH - RNA/*chemistry/*classification +MH - RNA, Catalytic/chemistry/metabolism +MH - Reproducibility of Results +MH - Ribosomes/chemistry/metabolism +MH - Riboswitch +PMC - PMC7293047 +EDAT- 2020/05/15 06:00 +MHDA- 2020/09/09 06:00 +PMCR- 2020/05/14 +CRDT- 2020/05/15 06:00 +PHST- 2020/04/30 00:00 [accepted] +PHST- 2020/04/11 00:00 [revised] +PHST- 2020/04/11 00:00 [received] +PHST- 2020/05/15 06:00 [pubmed] +PHST- 2020/09/09 06:00 [medline] +PHST- 2020/05/15 06:00 [entrez] +PHST- 2020/05/14 00:00 [pmc-release] +AID - 5837055 [pii] +AID - gkaa383 [pii] +AID - 10.1093/nar/gkaa383 [doi] +PST - ppublish +SO - Nucleic Acids Res. 2020 Jun 19;48(11):6367-6381. doi: 10.1093/nar/gkaa383. diff --git a/wrappers/dnatco/script/dnatco.py b/wrappers/dnatco/script/dnatco.py new file mode 100644 index 000000000..e9acac236 --- /dev/null +++ b/wrappers/dnatco/script/dnatco.py @@ -0,0 +1,107 @@ +""" + dnatco.py: CCP4 GUI Project + Copyright (C) 2025 MRC-LMB + Author: Martin Maly + + This library is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + version 3, modified in accordance with the provisions of the + license to address the requirements of UK law. + + You should have received a copy of the modified GNU Lesser General + Public License along with this library. If not, copies may be + downloaded from http://www.ccp4.ac.uk/ccp4license.php + + 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 Lesser General Public License for more details. +""" + +import os +import xml.etree.ElementTree as ET +from core.CCP4PluginScript import CPluginScript +from core.CCP4ErrorHandling import * +from core import CCP4Utils + + +class dnatco(CPluginScript): + + TASKMODULE = 'wrappers' # Where this plugin will appear on gui + TASKNAME = 'dnatco' # Task name - should be same as class name + TASKVERSION= 0.1 # Version of this plugin + TASKCOMMAND = '/opt/ccp4-9/bin/dnatco' # The command to run the executable + MAINTAINER = 'martin.maly@mrc-lmb.cam.ac.uk' + + ERROR_CODES = { 201 : { 'description' : 'No output restraint file from DNATCO' }, + 202 : { 'description' : 'No output extended mmCIF file from DNATCO' }, + 203 : { 'description' : 'No output PDF report file from DNATCO' }, + 204 : { 'description' : 'Failed to dnatcoify structure' }, + } + + + def makeCommandAndScript(self): + self.appendCommandLine(['--coords', str(self.container.inputData.XYZIN.fullPath)]) + self.appendCommandLine(['--outputDir', self.workDirectory]) + self.appendCommandLine(['--extendedCIF']) + # if self.container.inputData.HKLIN.isSet(): + # self.appendCommandLine(['--reflns', str(self.container.inputData.HKLIN.fullPath)]) + if bool(self.container.controlParameters.GENERATE_RESTRAINTS): + self.appendCommandLine(['--refmacRestraints']) + if float(self.container.controlParameters.MAX_RMSD) != 0.5: + self.appendCommandLine(['--restraintsRmsd', str(float(self.container.controlParameters.MAX_RMSD))]) + if float(self.container.controlParameters.RESTRAINTS_SIGMA) != 1.0: + self.appendCommandLine(['--restraintsSigmaFactor', str(float(self.container.controlParameters.RESTRAINTS_SIGMA))]) + + + def processOutputFiles(self): + # outputFilenamePrefix = str(os.path.splitext( + # os.path.basename(str( + # self.container.inputData.XYZIN.fullPath)))[0]).upper().replace("_1", "") # TODO + # outputFilenamePrefix = "1Q93" + + # check that DNATCO has finished successfully + logText = self.logFileText() + pyListLogLines = logText.split("\n") + for j, pyStrLine in enumerate(pyListLogLines): + if "Failed to dnatcoify structure" in pyStrLine: + self.appendErrorReport(204) + return CPluginScript.FAILED + + # outputCifFilename = outputFilenamePrefix + '_extended.cif' + # outputCifPath = os.path.normpath(os.path.join(self.getWorkDirectory(), outputCifFilename)) + import glob + cif_files = glob.glob(os.path.join(self.workDirectory, "*_extended.cif")) + if cif_files: + outputCifPath = cif_files[0] + else: + outputCifPath = "" + if os.path.isfile(outputCifPath): + self.container.outputData.CIFOUT.setFullPath(outputCifPath) + self.container.outputData.CIFOUT.annotation.set('Extended model (mmCIF format)') + else: + self.appendErrorReport(202, outputCifPath) + return CPluginScript.FAILED + + if bool(self.container.controlParameters.GENERATE_RESTRAINTS): + # outputRestraintsFilename = outputFilenamePrefix + '_restraints_refmac.txt' + # outputRestraintsPath = os.path.normpath(os.path.join(self.getWorkDirectory(), outputRestraintsFilename)) + cif_files = glob.glob(os.path.join(self.workDirectory, "*_restraints_refmac.txt")) + if cif_files: + outputRestraintsPath = cif_files[0] + else: + outputRestraintsPath = "" + if os.path.isfile(outputRestraintsPath): + self.container.outputData.RESTRAINTS.setFullPath(outputRestraintsPath) + self.container.outputData.RESTRAINTS.annotation.set('DNATCO restraints') + else: + self.appendErrorReport(201, outputRestraintsPath) + return CPluginScript.FAILED + + xmlText = "" + xmlroot = ET.fromstringlist(["", xmlText, ""]) + ET.indent(xmlroot, space="\t", level=0) + with open(self.makeFileName('PROGRAMXML'), 'w', encoding="utf-8") as programXML: + CCP4Utils.writeXML(programXML, ET.tostring(xmlroot)) + + return CPluginScript.SUCCEEDED diff --git a/wrappers/dnatco/script/dnatco_report.py b/wrappers/dnatco/script/dnatco_report.py new file mode 100644 index 000000000..613a9926a --- /dev/null +++ b/wrappers/dnatco/script/dnatco_report.py @@ -0,0 +1,349 @@ +""" + dnatco_report.py: CCP4 GUI Project + Copyright (C) 2025 MRC-LMB + Author: Martin Maly + + This library is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + version 3, modified in accordance with the provisions of the + license to address the requirements of UK law. + + You should have received a copy of the modified GNU Lesser General + Public License along with this library. If not, copies may be + downloaded from http://www.ccp4.ac.uk/ccp4license.php + + 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 Lesser General Public License for more details. +""" + +from report.CCP4ReportParser import * +from core import CCP4Modules +import os +import gemmi + + +class dnatco_report(Report): + TASKNAME= 'dnatco' + RUNNING = True + + def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): + Report.__init__( self, xmlnode=xmlnode, jobInfo=jobInfo, jobStatus=jobStatus, **kw) + + if jobStatus is None or jobStatus.lower() == 'nooutput': return + + projectid = self.jobInfo.get("projectid", None) + jobNumber = self.jobInfo.get("jobnumber", None) + jobId = self.jobInfo.get("jobid", None) + jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId=jobId) + self.jobLog = os.path.join(jobDirectory, "log.txt") + if jobStatus is not None and jobStatus.lower() == "running": + self.runningReport(parent=self) + else: + self.defaultReport(parent=self) + + + def runningReport(self, parent=None): + if parent is None: + parent = self + if os.path.isfile(self.jobLog): + jobLogFold = parent.addFold(label="DNATCO log", initiallyOpen=True) + jobLogFold.addPre("DNATCO is running...") + + + def defaultReport(self, parent=None, jobDirectories=[]): + if parent is None: + parent = self + self.addDiv(style="clear:both;") # gives space for the title + + if jobDirectories: + if len(jobDirectories) == 2: + compare_two = True + else: + compare_two = False + else: + # only one job directory + compare_two = False + jobId = self.jobInfo.get("jobid", None) + jobDirectories = [CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId)] + + # load data + import glob + + def get_cif_data(job_dir, parent): + cif_files = glob.glob(os.path.join(job_dir, "*_extended.cif")) + ciffile_path = cif_files[0] if cif_files else "" + if not os.path.isfile(ciffile_path): + noteDiv = parent.addDiv(style='font-size:110%;color:red;') + noteDiv.append( + f"DNATCO did not generate the extended CIF file in {job_dir}. No report can be generated.
" + " Please check the log files for more information." + ) + return None + return self.read_data_from_cif(ciffile_path) + + cif_data1 = get_cif_data(jobDirectories[0], parent) + if compare_two: + cif_data2 = get_cif_data(jobDirectories[1], parent) + + # draw report + overallFold = parent.addFold(label="Overall structure quality", initiallyOpen=True) + noteDiv = overallFold.addDiv(style='font-size:110%;') + noteDiv.append( + "Assignment of conformer class (NtC) to dinucleotide steps and the quality of fit between each" + " dinucleotide and the NtC reference structure, measured by RMSD and confal score." + ) + table = overallFold.addTable(title="Overall structure quality", transpose=True) + if compare_two: + table.addData(title="", data=["Model 1", "Model 2"]) + table.addData(title="No. NtC assigned", data=[cif_data1['overall_num_classified'], cif_data2['overall_num_classified']]) + table.addData(title="No. NtC close", data=[cif_data1['overall_num_unclassified_rmsd_close'], cif_data2['overall_num_unclassified_rmsd_close']]) + table.addData(title="No. NtC unassigned", data=[cif_data1['overall_num_unclassified'], cif_data2['overall_num_unclassified']]) + table.addData(title="No. steps with RMSD < 0.5 Å", data=[cif_data1["overall_num_rmsd_below_0p5"], cif_data2["overall_num_rmsd_below_0p5"]]) + table.addData(title="No. steps with RMSD 0.5-1 Å", data=[cif_data1["overall_num_rmsd_between_0p5_and_1"], cif_data2["overall_num_rmsd_between_0p5_and_1"]]) + table.addData(title="No. steps with RMSD > 1 Å", data=[cif_data1["overall_num_rmsd_higher_1"], cif_data2["overall_num_rmsd_higher_1"]]) + table.addData(title="Confal score", data=[cif_data1['overall_confal_score'], cif_data2['overall_confal_score']]) + table.addData(title="Confal score percentile", data=[cif_data1['overall_confal_percentile'], cif_data2['overall_confal_percentile']]) + else: + table.addData(title="No. NtC assigned", data=[cif_data1['overall_num_classified']]) + table.addData(title="No. NtC close", data=[cif_data1['overall_num_unclassified_rmsd_close']]) + table.addData(title="No. NtC unassigned", data=[cif_data1['overall_num_unclassified']]) + table.addData(title="No. steps with RMSD < 0.5 Å", data=[cif_data1["overall_num_rmsd_below_0p5"]]) + table.addData(title="No. steps with RMSD 0.5-1 Å", data=[cif_data1["overall_num_rmsd_between_0p5_and_1"]]) + table.addData(title="No. steps with RMSD > 1 Å", data=[cif_data1["overall_num_rmsd_higher_1"]]) + table.addData(title="Confal score", data=[cif_data1['overall_confal_score']]) + table.addData(title="Confal score percentile", data=[cif_data1['overall_confal_percentile']]) + + if compare_two: + model_label = " (model 1)" + else: + model_label = "" + outliersFold = parent.addFold(label="Dinucleotides outliers", initiallyOpen=True) + noteDiv = outliersFold.addDiv(style='font-size:110%;') + noteDiv.append( + "List all unassigned dinucleotide steps. Dinucleotide conformer (NtC)," + " resp. conformational alphabet of nucleic acids (CANA) classes in" + " the table below represent the closest NtC, resp. CANA class that would be assigned to the given" + " dinucleotide if all assignment criteria were met." + ) + if ( + (compare_two and len(cif_data2['idx_outliers']) > 0) + or (not compare_two and len(cif_data1['idx_outliers']) > 0) + ): + table = outliersFold.addTable(title="Dinucleotides outliers") + table.addData(title="Step ID", data=cif_data1['step_id_outliers']) + table.addData(title="Chain", data=cif_data1['chain_display_outliers']) + table.addData(title="Step", data=cif_data1['steps_outliers']) + table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_outliers']) + if compare_two: + table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_outliers']) + table.addData(title=f"Closest NtC1{model_label}", data=cif_data1['assigned_NtC_outliers']) + if compare_two: + table.addData(title="Closest NtC (model 2)", data=cif_data2['assigned_NtC_outliers']) + table.addData(title=f"RMSD to closest NtC representative (Å){model_label}", data=cif_data1['rmsd_NtC_outliers']) + if compare_two: + table.addData(title="RMSD to closest NtC representative (Å) (model 1)", data=cif_data2['rmsd_NtC_outliers']) + noteDiv = outliersFold.addDiv(style='font-size:110%;') + noteDiv.append( + "A more detailed analysis could be performed at the" + " DNATCO web server." + ) + else: + noteDiv = outliersFold.addDiv(style='font-size:110%;') + noteDiv.append("No dinucleotide outliers found.") + + improvablesFold = parent.addFold(label="Improvable dinucleotide outliers", initiallyOpen=True) + noteDiv = improvablesFold.addDiv(style='font-size:110%;') + noteDiv.append( + "List of unassigned dinucleotide steps that are considered" + " sufficiently close to a representative from the Golden Set. Closeness criterion is defined as RMSD" + " value less or equal to 0.5 Å." + ) + if ( + (compare_two and len(cif_data2['idx_improvables']) > 0) + or (not compare_two and len(cif_data1['idx_improvables']) > 0) + ): + table = improvablesFold.addTable(title="Improvable dinucleotide outliers") + table.addData(title="Step ID", data=cif_data1['step_id_improvables']) + table.addData(title="Chain", data=cif_data1['chain_display_improvables']) + table.addData(title="Step", data=cif_data1['steps_improvables']) + table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_improvables']) + if compare_two: + table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_improvables']) + table.addData(title=f"Closest NtC{model_label}", data=cif_data1['assigned_NtC_improvables']) + if compare_two: + table.addData(title="Closest NtC (model 2)", data=cif_data2['assigned_NtC_improvables']) + table.addData(title=f"RMSD to closest NtC representative (Å){model_label}", data=cif_data1['rmsd_NtC_improvables']) + if compare_two: + table.addData(title="RMSD to closest NtC representative (Å) (model 1)", data=cif_data2['rmsd_NtC_improvables']) + noteDiv = improvablesFold.addDiv(style='font-size:110%;') + noteDiv.append( + "A more detailed analysis could be performed at the" + " DNATCO web server." + ) + else: + noteDiv = improvablesFold.addDiv(style='font-size:110%;') + noteDiv.append("No improvable dinucleotide outliers found.") + + allDinucleotidesFold = parent.addFold(label="All dinucleotides", initiallyOpen=True) + + graphPerStep = allDinucleotidesFold.addFlotGraph( + title="RMSD per step", + style="height:330px; width:585px; border:0px; padding:10px; padding-left:15px; margin-right:15px;") + graphPerStep.addData(title="step", data=cif_data1['step_id']) + graphPerStep.addData(title="RMSD_model1(A)", data=cif_data1['rmsd']) + if compare_two: + graphPerStep.addData(title="RMSD_model2(A)", data=cif_data2['rmsd']) + plotPerStep = graphPerStep.addPlotObject() + plotPerStep.append('title', 'RMSD per step') + plotPerStep.append('plottype', 'xy') + plotPerStep.append('xlabel', 'step id') + plotPerStep.append('ylabel', 'RMSD (A)') + plotPerStep.append('legendposition', x=1, y=0) # right bottom corner + plotLine = plotPerStep.append('plotline', xcol=1, ycol=2) + plotLine.append('symbolsize', '1') + plotLine.append('linestyle', '.') + plotLine.append('colour', 'gray') + if compare_two: + plotLine2 = plotPerStep.append('plotline', xcol=1, ycol=3) + plotLine2.append('symbolsize', '1') + plotLine2.append('linestyle', '.') + plotLine2.append('colour', 'blue') + + table = allDinucleotidesFold.addTable(title="All dinucleotides") + table.addData(title="Step ID", data=cif_data1['step_id']) + table.addData(title="Chain", data=cif_data1['chain_display']) + table.addData(title="Step", data=cif_data1['steps']) + table.addData(title=f"Assigned CANA{model_label}", data=cif_data1['assigned_CANA']) + if compare_two: + table.addData(title="Assigned CANA (model 2)", data=cif_data2['assigned_CANA']) + table.addData(title=f"Assigned NtC{model_label}", data=cif_data1['assigned_NtC']) + if compare_two: + table.addData(title="Assigned NtC (model 2)", data=cif_data2['assigned_NtC']) + table.addData(title=f"Confal score{model_label}", data=cif_data1['confal_score']) + if compare_two: + table.addData(title="Confal score (model 2)", data=cif_data2['rmsd_NtC_assigned']) + table.addData(title=f"RMSD to closest NtC representative (Å){model_label}", data=cif_data1['rmsd_NtC_assigned']) + if compare_two: + table.addData(title="RMSD to closest NtC representative (Å) (model 2)", data=cif_data2['rmsd_NtC_assigned']) + + self.addDiv(style="clear:both;") + + + def read_data_from_cif(self, ciffilePath): + ciffile = gemmi.cif.read_file(ciffilePath) + cif_data = {} + + cif_data['overall_confal_score'] = ciffile[0].find_value('_ndb_struct_ntc_overall.confal_score') + cif_data['overall_confal_percentile'] = ciffile[0].find_value('_ndb_struct_ntc_overall.confal_percentile') + cif_data['overall_num_classified'] = ciffile[0].find_value('_ndb_struct_ntc_overall.num_classified') + cif_data['overall_num_unclassified'] = ciffile[0].find_value('_ndb_struct_ntc_overall.num_unclassified') + cif_data['overall_num_unclassified_rmsd_close'] = ciffile[0].find_value( + '_ndb_struct_ntc_overall.num_unclassified_rmsd_close' + ) + + table_step_id = ciffile[0].find( + '_ndb_struct_ntc_step.', + ['id', + 'name', + 'PDB_model_number', + 'label_entity_id_1', + 'label_asym_id_1', + 'label_seq_id_1', + 'label_comp_id_1', + 'label_alt_id_1', + 'label_entity_id_2', + 'label_asym_id_2', + 'label_seq_id_2', + 'label_comp_id_2', + 'label_alt_id_2', + 'auth_asym_id_1', + 'auth_seq_id_1', + 'auth_asym_id_2', + 'auth_seq_id_2', + 'PDB_ins_code_1', + 'PDB_ins_code_2'] + ) + cif_data['chain_label'] = table_step_id.find_column('label_asym_id_1') + cif_data['chain_auth'] = table_step_id.find_column('auth_asym_id_1') + cif_data['chain_display'] = [ + f"{label}" + if label == auth + else f"{label} (auth: {auth})" for label, auth in zip(cif_data['chain_label'], cif_data['chain_auth']) + ] + cif_data['comp_id_1'] = table_step_id.find_column('label_comp_id_1') + cif_data['auth_seq_id_1'] = table_step_id.find_column('auth_seq_id_1') + cif_data['comp_id_2'] = table_step_id.find_column('label_comp_id_2') + cif_data['auth_seq_id_2'] = table_step_id.find_column('auth_seq_id_2') + cif_data['steps'] = [f"{comp_id_1}{seq_id_1} {comp_id_2}{seq_id_2}" for comp_id_1, seq_id_1, comp_id_2, seq_id_2 in zip( + cif_data['comp_id_1'], cif_data['auth_seq_id_1'], cif_data['comp_id_2'], cif_data['auth_seq_id_2'])] + + table_step = ciffile[0].find( + '_ndb_struct_ntc_step_summary.', + ['step_id', + 'assigned_CANA', + 'assigned_NtC', + 'confal_score', + 'euclidean_distance_NtC_ideal', + 'cartesian_rmsd_closest_NtC_representative', + 'closest_CANA', + 'closest_NtC', + 'closest_step_golden'] + ) + cif_data['step_id'] = table_step.find_column('step_id') + cif_data['assigned_CANA'] = table_step.find_column('assigned_CANA') + cif_data['assigned_NtC'] = table_step.find_column('assigned_NtC') + cif_data['confal_score'] = table_step.find_column('confal_score') + # cif_data['euclidean_distance_NtC_ideal'] = table_step.find_column('euclidean_distance_NtC_ideal') + cif_data['rmsd'] = table_step.find_column('cartesian_rmsd_closest_NtC_representative') + cif_data['closest_NtC'] = table_step.find_column('closest_NtC') + cif_data['closest_CANA'] = table_step.find_column('closest_CANA') + cif_data['rmsd_NtC_assigned'] = [ + f"{float(rmsd):.2f} ({closest_NtC})" + if closest_NtC != '.' + else f"{float(rmsd):.2f}" for rmsd, closest_NtC in zip(cif_data['rmsd'], cif_data['closest_NtC']) + ] + cif_data['idx_rmsd_below_0p5'] = [ + i for i, rmsd in enumerate(cif_data['rmsd']) + if rmsd not in ('.', '?') and float(rmsd) <= 0.5 + ] + cif_data['idx_rmsd_between_0p5_and_1'] = [ + i for i, rmsd in enumerate(cif_data['rmsd']) + if rmsd not in ('.', '?') and 0.5 < float(rmsd) <= 1.0 + ] + cif_data['idx_rmsd_higher_1'] = [ + i for i, rmsd in enumerate(cif_data['rmsd']) + if rmsd not in ('.', '?') and float(rmsd) > 1.0 + ] + cif_data["overall_num_rmsd_below_0p5"] = len(cif_data['idx_rmsd_below_0p5']) + cif_data["overall_num_rmsd_between_0p5_and_1"] = len(cif_data['idx_rmsd_between_0p5_and_1']) + cif_data["overall_num_rmsd_higher_1"]= len(cif_data['idx_rmsd_higher_1']) + + # Filter data based on CANA/NtC assignment and RMSD values + cif_data['idx_improvables'] = [] + cif_data['idx_outliers'] = [] + for i, (ntc, rmsd) in enumerate(zip(cif_data['assigned_NtC'], cif_data['rmsd'])): + if ntc == 'NANT': + if rmsd not in ('.', '?') and rmsd.isnumeric() and float(rmsd) <= 0.5: + cif_data['idx_improvables'].append(i) + else: + cif_data['idx_outliers'].append(i) + cif_data['step_id_improvables'] = [cif_data['step_id'][i] for i in cif_data['idx_improvables']] + cif_data['chain_display_improvables'] = [cif_data['chain_display'][i] for i in cif_data['idx_improvables']] + cif_data['steps_improvables'] = [cif_data['steps'][i] for i in cif_data['idx_improvables']] + cif_data['assigned_CANA_improvables'] = [cif_data['closest_CANA'][i] for i in cif_data['idx_improvables']] + cif_data['assigned_NtC_improvables'] = [cif_data['closest_NtC'][i] for i in cif_data['idx_improvables']] + cif_data['rmsd_NtC_improvables'] = [cif_data['rmsd'][i] for i in cif_data['idx_improvables']] + + cif_data['step_id_outliers'] = [cif_data['step_id'][i] for i in cif_data['idx_outliers']] + cif_data['chain_display_outliers'] = [cif_data['chain_display'][i] for i in cif_data['idx_outliers']] + cif_data['steps_outliers'] = [cif_data['steps'][i] for i in cif_data['idx_outliers']] + cif_data['assigned_CANA_outliers'] = [ cif_data['closest_CANA'][i] for i in cif_data['idx_outliers']] + cif_data['assigned_NtC_outliers'] = [cif_data['closest_NtC'][i] for i in cif_data['idx_outliers']] + cif_data['rmsd_NtC_outliers'] = [cif_data['rmsd'][i] for i in cif_data['idx_outliers']] + + cif_data['assigned_CANA'] = [cana if cana != 'NAN' else '-' for cana in cif_data['assigned_CANA']] + cif_data['assigned_NtC'] = [ntc if ntc != 'NANT' else '-' for ntc in cif_data['assigned_NtC']] + + return cif_data diff --git a/wrappers/servalcat/script/servalcat.def.xml b/wrappers/servalcat/script/servalcat.def.xml index cecfdc215..0cccfc654 100644 --- a/wrappers/servalcat/script/servalcat.def.xml +++ b/wrappers/servalcat/script/servalcat.def.xml @@ -92,6 +92,16 @@ See the "Generate a Free R set" task in the "Data reduction" section. MetalCoord restraints for metal sites
+ + CRefmacRestraintsDataFile + + True + True + True + True + DNATCO restraints for nucleic acids + + CRefmacRestraintsDataFile diff --git a/wrappers/servalcat/script/servalcat.py b/wrappers/servalcat/script/servalcat.py index ed3ed8f53..a873f29db 100644 --- a/wrappers/servalcat/script/servalcat.py +++ b/wrappers/servalcat/script/servalcat.py @@ -1,6 +1,6 @@ """ servalcat.py: CCP4 GUI Project - Copyright (C) 2024 MRC-LBM, University of Southampton + Copyright (C) 2024 University of Southampton, MRC-LMB Cambridge This library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License @@ -469,7 +469,7 @@ def makeCommandAndScript(self): keywordFilePath = str(os.path.join(self.getWorkDirectory(), 'keywords.txt')) - if str(float(self.container.controlParameters.VDWR_WEIGHT)) != "1": + if str(float(self.container.controlParameters.VDWR_WEIGHT)) != "1.0": with open(keywordFilePath, "a+") as keywordFile: keywordFile.write("VDWR " + str(self.container.controlParameters.VDWR_WEIGHT) + "\n") # Occupancy refinement @@ -504,6 +504,9 @@ def makeCommandAndScript(self): if self.container.inputData.METALCOORD_RESTRAINTS.isSet(): with open(keywordFilePath, "a+") as keywordFile: keywordFile.write("\n@%s"%(str(self.container.inputData.METALCOORD_RESTRAINTS.fullPath))) + if self.container.inputData.DNATCO_RESTRAINTS.isSet(): + with open(keywordFilePath, "a+") as keywordFile: + keywordFile.write("\n@%s"%(str(self.container.inputData.DNATCO_RESTRAINTS.fullPath))) if self.container.inputData.PROSMART_PROTEIN_RESTRAINTS.isSet(): with open(keywordFilePath, "a+") as keywordFile: if self.container.controlParameters.PROSMART_PROTEIN_SGMN.isSet(): @@ -576,6 +579,6 @@ def flushXml(self): # assumes self.xmlroot and self.xmlLength are well set CCP4Utils.writeXML(programXmlFile, newXml) shutil.move(self.makeFileName('PROGRAMXML')+'_tmp', self.makeFileName('PROGRAMXML')) - def setProgramVersion(self): - print('refmac.getProgramVersion') - return CPluginScript.setProgramVersion(self,'Refmac_5') + """def setProgramVersion(self): + print('servalcat.getProgramVersion') + return CPluginScript.setProgramVersion(self,'servalcat')""" diff --git a/wrappers/servalcat/script/servalcat_report.py b/wrappers/servalcat/script/servalcat_report.py index d24d072ad..d3fa29e99 100644 --- a/wrappers/servalcat/script/servalcat_report.py +++ b/wrappers/servalcat/script/servalcat_report.py @@ -1,3 +1,22 @@ +""" + servalcat_report.py: CCP4 GUI Project + Copyright (C) 2024 University of Southampton, MRC-LMB Cambridge + + This library is free software: you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License + version 3, modified in accordance with the provisions of the + license to address the requirements of UK law. + + You should have received a copy of the modified GNU Lesser General + Public License along with this library. If not, copies may be + downloaded from http://www.ccp4.ac.uk/ccp4license.php + + This program is distributed in the hope that it will be useful,S + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + """ + from report.CCP4ReportParser import * import sys from xml.etree import ElementTree as ET From 11643238902f11240dfec61bc0e9113b8b2a8da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Wed, 9 Jul 2025 16:32:03 +0100 Subject: [PATCH 02/21] use pathlib instead of os.path, catch properly output CIFFILE paths in reports --- .../dnatco_pipe/script/dnatco_pipe.def.xml | 20 ++++++++ pipelines/dnatco_pipe/script/dnatco_pipe.py | 24 +++++++-- .../dnatco_pipe/script/dnatco_pipe_report.py | 22 ++++---- wrappers/dnatco/script/dnatco.def.xml | 11 +--- wrappers/dnatco/script/dnatco.py | 27 ++++------ wrappers/dnatco/script/dnatco_report.py | 51 +++++++++---------- 6 files changed, 85 insertions(+), 70 deletions(-) diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml b/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml index 700489fb5..3601643c5 100644 --- a/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml +++ b/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml @@ -39,6 +39,26 @@
+ + CPdbDataFile + + + 1 + 2 + + True + + + + CPdbDataFile + + + 1 + 2 + + True + + CRefmacRestraintsDataFile diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe.py b/pipelines/dnatco_pipe/script/dnatco_pipe.py index bdf56e466..8bd169472 100644 --- a/pipelines/dnatco_pipe/script/dnatco_pipe.py +++ b/pipelines/dnatco_pipe/script/dnatco_pipe.py @@ -18,7 +18,7 @@ GNU Lesser General Public License for more details. """ -import os +from pathlib import Path import shutil import xml.etree.ElementTree as ET from PySide2 import QtCore @@ -46,7 +46,7 @@ def process(self): self.dnatco1.process() if ( bool(self.container.controlParameters.TOGGLE_XYZIN2) - and os.path.isfile(str(self.container.inputData.XYZIN2.fullPath)) + and Path(str(self.container.inputData.XYZIN2.fullPath)).is_file() ): self.dnatco2 = self.dnatcoCreate(self.container.inputData.XYZIN2) self.dnatco2.process() @@ -79,12 +79,30 @@ def process_finish(self): with open(self.makeFileName('PROGRAMXML'), 'w', encoding="utf-8") as programXML: CCP4Utils.writeXML(programXML, ET.tostring(xmlroot)) + + ciffile1PathJob = str(self.dnatco1.container.outputData.CIFOUT.fullPath) + ciffile1PathPipeline = str(self.container.outputData.CIFOUT1.fullPath) + if Path(ciffile1PathJob).is_file(): + shutil.copyfile(ciffile1PathJob, ciffile1PathPipeline) + self.container.outputData.CIFOUT1.annotation.set(self.dnatco1.container.outputData.CIFOUT.annotation) + if bool(self.container.controlParameters.TOGGLE_XYZIN2): + ciffile2PathJob = str(self.dnatco2.container.outputData.CIFOUT.fullPath) + ciffile2PathPipeline = str(self.container.outputData.CIFOUT2.fullPath) + if Path(ciffile2PathJob).is_file(): + shutil.copyfile(ciffile2PathJob, ciffile2PathPipeline) + self.container.outputData.CIFOUT1.annotation.set( + str(self.dnatco1.container.outputData.CIFOUT.annotation).replace("model", "model 1") + ) + self.container.outputData.CIFOUT2.annotation.set( + str(self.dnatco2.container.outputData.CIFOUT.annotation).replace("model", "model 2") + ) + if bool(self.container.controlParameters.GENERATE_RESTRAINTS): dnatco_obj = self.dnatco2 if bool(self.container.controlParameters.TOGGLE_XYZIN2) else self.dnatco1 self.container.outputData.RESTRAINTS.annotation.set(dnatco_obj.container.outputData.RESTRAINTS.annotation) restraintsPathJob = str(dnatco_obj.container.outputData.RESTRAINTS.fullPath) restraintsPathPipeline = str(self.container.outputData.RESTRAINTS.fullPath) - if os.path.isfile(restraintsPathJob): + if Path(restraintsPathJob).is_file(): shutil.copyfile(restraintsPathJob, restraintsPathPipeline) self.reportStatus(CPluginScript.SUCCEEDED) diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe_report.py b/pipelines/dnatco_pipe/script/dnatco_pipe_report.py index c11dee704..74208131e 100644 --- a/pipelines/dnatco_pipe/script/dnatco_pipe_report.py +++ b/pipelines/dnatco_pipe/script/dnatco_pipe_report.py @@ -21,7 +21,7 @@ from report.CCP4ReportParser import * from core import CCP4Modules from wrappers.dnatco.script.dnatco_report import dnatco_report -import os +from pathlib import Path class dnatco_pipe_report(Report): @@ -37,7 +37,7 @@ def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): jobNumber = self.jobInfo.get("jobnumber", None) jobId = self.jobInfo.get("jobid", None) jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId=jobId) - self.jobLog = os.path.join(jobDirectory, "log.txt") + self.jobLog = str(Path(jobDirectory) / "log.txt") if jobStatus is not None and jobStatus.lower() == "running": self.runningReport(parent=self) else: @@ -47,7 +47,7 @@ def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): def runningReport(self, parent=None): if parent is None: parent = self - if os.path.isfile(self.jobLog): + if Path(self.jobLog).is_file(): jobLogFold = parent.addFold(label="DNATCO log", initiallyOpen=True) jobLogFold.addPre("DNATCO is running...") @@ -57,14 +57,10 @@ def defaultReport(self, parent=None): parent = self self.addDiv(style="clear:both;") # gives space for the title - jobId = self.jobInfo.get("jobid", None) - jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId) - - jobDirectory1 = os.path.join(jobDirectory, "job_1") # TODO: derive from jobId? - jobDirectory2 = os.path.join(jobDirectory, "job_2") # TODO: derive from jobId? - jobDirectories = [jobDirectory1] - if os.path.isdir(jobDirectory2): - jobDirectories.append(jobDirectory2) - + ciffile1Path = str(self.jobInfo['filenames']['CIFOUT1']) + ciffile2Path = str(self.jobInfo['filenames']['CIFOUT2']) + ciffilePaths = [ciffile1Path] + if Path(ciffile2Path).is_file(): + ciffilePaths.append(ciffile2Path) dnatco_report1 = dnatco_report() - dnatco_report1.defaultReport(parent, jobDirectories) \ No newline at end of file + dnatco_report1.defaultReport(parent, ciffilePaths) \ No newline at end of file diff --git a/wrappers/dnatco/script/dnatco.def.xml b/wrappers/dnatco/script/dnatco.def.xml index 92b6f8d7d..f3bbc5112 100644 --- a/wrappers/dnatco/script/dnatco.def.xml +++ b/wrappers/dnatco/script/dnatco.def.xml @@ -45,18 +45,9 @@ 1 2 - False + True - - CPDFDataFile - - - DNATCO report (PDF) - - False - - diff --git a/wrappers/dnatco/script/dnatco.py b/wrappers/dnatco/script/dnatco.py index e9acac236..bed305ca0 100644 --- a/wrappers/dnatco/script/dnatco.py +++ b/wrappers/dnatco/script/dnatco.py @@ -18,7 +18,7 @@ GNU Lesser General Public License for more details. """ -import os +from pathlib import Path import xml.etree.ElementTree as ET from core.CCP4PluginScript import CPluginScript from core.CCP4ErrorHandling import * @@ -70,28 +70,23 @@ def processOutputFiles(self): # outputCifFilename = outputFilenamePrefix + '_extended.cif' # outputCifPath = os.path.normpath(os.path.join(self.getWorkDirectory(), outputCifFilename)) - import glob - cif_files = glob.glob(os.path.join(self.workDirectory, "*_extended.cif")) - if cif_files: - outputCifPath = cif_files[0] - else: - outputCifPath = "" - if os.path.isfile(outputCifPath): - self.container.outputData.CIFOUT.setFullPath(outputCifPath) - self.container.outputData.CIFOUT.annotation.set('Extended model (mmCIF format)') - else: - self.appendErrorReport(202, outputCifPath) + work_dir = Path(self.workDirectory) + cif_files = list(work_dir.glob("*_extended.cif")) + if not cif_files or not Path(str(cif_files[0])).is_file(): + self.appendErrorReport(202, str(cif_files[0])) return CPluginScript.FAILED + self.container.outputData.CIFOUT.setFullPath(str(cif_files[0])) + self.container.outputData.CIFOUT.annotation.set('Extended model (mmCIF format)') if bool(self.container.controlParameters.GENERATE_RESTRAINTS): # outputRestraintsFilename = outputFilenamePrefix + '_restraints_refmac.txt' # outputRestraintsPath = os.path.normpath(os.path.join(self.getWorkDirectory(), outputRestraintsFilename)) - cif_files = glob.glob(os.path.join(self.workDirectory, "*_restraints_refmac.txt")) - if cif_files: - outputRestraintsPath = cif_files[0] + restraint_files = list(work_dir.glob("*_restraints_refmac.txt")) + if restraint_files: + outputRestraintsPath = str(restraint_files[0]) else: outputRestraintsPath = "" - if os.path.isfile(outputRestraintsPath): + if Path(outputRestraintsPath).is_file(): self.container.outputData.RESTRAINTS.setFullPath(outputRestraintsPath) self.container.outputData.RESTRAINTS.annotation.set('DNATCO restraints') else: diff --git a/wrappers/dnatco/script/dnatco_report.py b/wrappers/dnatco/script/dnatco_report.py index 613a9926a..592ed59d5 100644 --- a/wrappers/dnatco/script/dnatco_report.py +++ b/wrappers/dnatco/script/dnatco_report.py @@ -20,7 +20,7 @@ from report.CCP4ReportParser import * from core import CCP4Modules -import os +from pathlib import Path import gemmi @@ -37,7 +37,7 @@ def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): jobNumber = self.jobInfo.get("jobnumber", None) jobId = self.jobInfo.get("jobid", None) jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId=jobId) - self.jobLog = os.path.join(jobDirectory, "log.txt") + self.jobLog = str(Path(jobDirectory) / "log.txt") if jobStatus is not None and jobStatus.lower() == "running": self.runningReport(parent=self) else: @@ -47,45 +47,32 @@ def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): def runningReport(self, parent=None): if parent is None: parent = self - if os.path.isfile(self.jobLog): + if Path(self.jobLog).is_file(): jobLogFold = parent.addFold(label="DNATCO log", initiallyOpen=True) jobLogFold.addPre("DNATCO is running...") - def defaultReport(self, parent=None, jobDirectories=[]): + def defaultReport(self, parent=None, ciffilePaths=[]): + if parent is None: parent = self self.addDiv(style="clear:both;") # gives space for the title - if jobDirectories: - if len(jobDirectories) == 2: + if ciffilePaths: + if len(ciffilePaths) == 2: compare_two = True else: compare_two = False else: - # only one job directory + # only one mmCIF file from this job compare_two = False - jobId = self.jobInfo.get("jobid", None) - jobDirectories = [CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId)] - - # load data - import glob - - def get_cif_data(job_dir, parent): - cif_files = glob.glob(os.path.join(job_dir, "*_extended.cif")) - ciffile_path = cif_files[0] if cif_files else "" - if not os.path.isfile(ciffile_path): - noteDiv = parent.addDiv(style='font-size:110%;color:red;') - noteDiv.append( - f"DNATCO did not generate the extended CIF file in {job_dir}. No report can be generated.
" - " Please check the log files for more information." - ) - return None - return self.read_data_from_cif(ciffile_path) - - cif_data1 = get_cif_data(jobDirectories[0], parent) + if self.jobInfo: + if 'filenames' in self.jobInfo and 'CIFOUT' in self.jobInfo['filenames']: + ciffilePaths = [self.jobInfo['filenames']['CIFOUT']] + + cif_data1 = self.read_data_from_cif(ciffilePaths[0], parent) if compare_two: - cif_data2 = get_cif_data(jobDirectories[1], parent) + cif_data2 = self.read_data_from_cif(ciffilePaths[1], parent) # draw report overallFold = parent.addFold(label="Overall structure quality", initiallyOpen=True) @@ -231,7 +218,15 @@ def get_cif_data(job_dir, parent): self.addDiv(style="clear:both;") - def read_data_from_cif(self, ciffilePath): + def read_data_from_cif(self, ciffilePath, parent=None): + if not Path(ciffilePath).is_file(): + noteDiv = parent.addDiv(style='font-size:110%;color:red;') + noteDiv.append( + f"DNATCO did not generate the extended CIF file (expected path: {ciffilePath})." + " No report could be generated.
" + " Please check the log files for more information." + ) + return None ciffile = gemmi.cif.read_file(ciffilePath) cif_data = {} From 07ebfe30663eac51f937d847fbe0114ba1e84cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Fri, 11 Jul 2025 10:35:12 +0100 Subject: [PATCH 03/21] hide CANA in the report --- wrappers/dnatco/script/dnatco_report.py | 38 +++++++++++++------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/wrappers/dnatco/script/dnatco_report.py b/wrappers/dnatco/script/dnatco_report.py index 592ed59d5..ea3300a49 100644 --- a/wrappers/dnatco/script/dnatco_report.py +++ b/wrappers/dnatco/script/dnatco_report.py @@ -109,9 +109,11 @@ def defaultReport(self, parent=None, ciffilePaths=[]): outliersFold = parent.addFold(label="Dinucleotides outliers", initiallyOpen=True) noteDiv = outliersFold.addDiv(style='font-size:110%;') noteDiv.append( - "List all unassigned dinucleotide steps. Dinucleotide conformer (NtC)," - " resp. conformational alphabet of nucleic acids (CANA) classes in" - " the table below represent the closest NtC, resp. CANA class that would be assigned to the given" + "List all unassigned dinucleotide steps. Dinucleotide conformer (NtC)" + #", resp. conformational alphabet of nucleic acids (CANA) classes " + " in the table below represent the closest NtC" + #", resp. CANA class" + " that would be assigned to the given" " dinucleotide if all assignment criteria were met." ) if ( @@ -122,9 +124,9 @@ def defaultReport(self, parent=None, ciffilePaths=[]): table.addData(title="Step ID", data=cif_data1['step_id_outliers']) table.addData(title="Chain", data=cif_data1['chain_display_outliers']) table.addData(title="Step", data=cif_data1['steps_outliers']) - table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_outliers']) - if compare_two: - table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_outliers']) + #table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_outliers']) + #if compare_two: + # table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_outliers']) table.addData(title=f"Closest NtC1{model_label}", data=cif_data1['assigned_NtC_outliers']) if compare_two: table.addData(title="Closest NtC (model 2)", data=cif_data2['assigned_NtC_outliers']) @@ -155,9 +157,9 @@ def defaultReport(self, parent=None, ciffilePaths=[]): table.addData(title="Step ID", data=cif_data1['step_id_improvables']) table.addData(title="Chain", data=cif_data1['chain_display_improvables']) table.addData(title="Step", data=cif_data1['steps_improvables']) - table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_improvables']) - if compare_two: - table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_improvables']) + #table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_improvables']) + #if compare_two: + # table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_improvables']) table.addData(title=f"Closest NtC{model_label}", data=cif_data1['assigned_NtC_improvables']) if compare_two: table.addData(title="Closest NtC (model 2)", data=cif_data2['assigned_NtC_improvables']) @@ -202,9 +204,9 @@ def defaultReport(self, parent=None, ciffilePaths=[]): table.addData(title="Step ID", data=cif_data1['step_id']) table.addData(title="Chain", data=cif_data1['chain_display']) table.addData(title="Step", data=cif_data1['steps']) - table.addData(title=f"Assigned CANA{model_label}", data=cif_data1['assigned_CANA']) - if compare_two: - table.addData(title="Assigned CANA (model 2)", data=cif_data2['assigned_CANA']) + #table.addData(title=f"Assigned CANA{model_label}", data=cif_data1['assigned_CANA']) + #if compare_two: + # table.addData(title="Assigned CANA (model 2)", data=cif_data2['assigned_CANA']) table.addData(title=f"Assigned NtC{model_label}", data=cif_data1['assigned_NtC']) if compare_two: table.addData(title="Assigned NtC (model 2)", data=cif_data2['assigned_NtC']) @@ -277,7 +279,7 @@ def read_data_from_cif(self, ciffilePath, parent=None): table_step = ciffile[0].find( '_ndb_struct_ntc_step_summary.', ['step_id', - 'assigned_CANA', + #'assigned_CANA', 'assigned_NtC', 'confal_score', 'euclidean_distance_NtC_ideal', @@ -287,13 +289,13 @@ def read_data_from_cif(self, ciffilePath, parent=None): 'closest_step_golden'] ) cif_data['step_id'] = table_step.find_column('step_id') - cif_data['assigned_CANA'] = table_step.find_column('assigned_CANA') + #cif_data['assigned_CANA'] = table_step.find_column('assigned_CANA') cif_data['assigned_NtC'] = table_step.find_column('assigned_NtC') cif_data['confal_score'] = table_step.find_column('confal_score') # cif_data['euclidean_distance_NtC_ideal'] = table_step.find_column('euclidean_distance_NtC_ideal') cif_data['rmsd'] = table_step.find_column('cartesian_rmsd_closest_NtC_representative') cif_data['closest_NtC'] = table_step.find_column('closest_NtC') - cif_data['closest_CANA'] = table_step.find_column('closest_CANA') + #cif_data['closest_CANA'] = table_step.find_column('closest_CANA') cif_data['rmsd_NtC_assigned'] = [ f"{float(rmsd):.2f} ({closest_NtC})" if closest_NtC != '.' @@ -327,18 +329,18 @@ def read_data_from_cif(self, ciffilePath, parent=None): cif_data['step_id_improvables'] = [cif_data['step_id'][i] for i in cif_data['idx_improvables']] cif_data['chain_display_improvables'] = [cif_data['chain_display'][i] for i in cif_data['idx_improvables']] cif_data['steps_improvables'] = [cif_data['steps'][i] for i in cif_data['idx_improvables']] - cif_data['assigned_CANA_improvables'] = [cif_data['closest_CANA'][i] for i in cif_data['idx_improvables']] + #cif_data['assigned_CANA_improvables'] = [cif_data['closest_CANA'][i] for i in cif_data['idx_improvables']] cif_data['assigned_NtC_improvables'] = [cif_data['closest_NtC'][i] for i in cif_data['idx_improvables']] cif_data['rmsd_NtC_improvables'] = [cif_data['rmsd'][i] for i in cif_data['idx_improvables']] cif_data['step_id_outliers'] = [cif_data['step_id'][i] for i in cif_data['idx_outliers']] cif_data['chain_display_outliers'] = [cif_data['chain_display'][i] for i in cif_data['idx_outliers']] cif_data['steps_outliers'] = [cif_data['steps'][i] for i in cif_data['idx_outliers']] - cif_data['assigned_CANA_outliers'] = [ cif_data['closest_CANA'][i] for i in cif_data['idx_outliers']] + #cif_data['assigned_CANA_outliers'] = [ cif_data['closest_CANA'][i] for i in cif_data['idx_outliers']] cif_data['assigned_NtC_outliers'] = [cif_data['closest_NtC'][i] for i in cif_data['idx_outliers']] cif_data['rmsd_NtC_outliers'] = [cif_data['rmsd'][i] for i in cif_data['idx_outliers']] - cif_data['assigned_CANA'] = [cana if cana != 'NAN' else '-' for cana in cif_data['assigned_CANA']] + #cif_data['assigned_CANA'] = [cana if cana != 'NAN' else '-' for cana in cif_data['assigned_CANA']] cif_data['assigned_NtC'] = [ntc if ntc != 'NANT' else '-' for ntc in cif_data['assigned_NtC']] return cif_data From 2a70a99df11481750849126b198ec6991db14e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Wed, 16 Jul 2025 10:54:52 +0100 Subject: [PATCH 04/21] dnatco i2run tests --- test/i2run/test_dnatco.py | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 test/i2run/test_dnatco.py diff --git a/test/i2run/test_dnatco.py b/test/i2run/test_dnatco.py new file mode 100644 index 000000000..e1f6f18e6 --- /dev/null +++ b/test/i2run/test_dnatco.py @@ -0,0 +1,43 @@ +from pathlib import Path +import gemmi +from .urls import rcsb_mmcif +from .utils import download, i2run + + +def test_rRNA(): + with download(rcsb_mmcif("1q93")) as mmcif_1q93: + args = ["dnatco_pipe"] + args += ["--XYZIN1", mmcif_1q93] + args += ["--GENERATE_RESTRAINTS", "True"] + with i2run(args) as job: + check_output(job) + + +def test_2rRNA(): + with download(rcsb_mmcif("1q93")) as mmcif_1q93, download(rcsb_mmcif("1q96")) as mmcif_1q96: + args = ["dnatco_pipe"] + args += ["--XYZIN1", mmcif_1q93] + args += ["--TOGGLE_XYZIN2", "True"] + args += ["--XYZIN2", mmcif_1q96] + args += ["--GENERATE_RESTRAINTS", "True"] + args += ["--MAX_RMSD", "0.49"] + args += ["--RESTRAINTS_SIGMA", "0.9"] + with i2run(args) as job: + check_output(job, nCiffiles=1) + + +def check_output(job: Path, nCiffiles=1): + assert (job / "RESTRAINTS.txt").is_file(), "Restraints output file not found" + for i in range(nCiffiles): + ciffilePath = job / f"CIFOUT{i + 1}.pdb" + assert ciffilePath.is_file(), "Extended CIF output file not found" + gemmi.read_structure(str(ciffilePath), format=gemmi.CoorFormat.Mmcif) + ciffile = gemmi.cif.read_file(str(ciffilePath)) + values = ['_ndb_struct_ntc_overall.confal_score', + '_ndb_struct_ntc_overall.confal_percentile', + '_ndb_struct_ntc_overall.num_classified', + '_ndb_struct_ntc_overall.num_unclassified', + '_ndb_struct_ntc_overall.num_unclassified_rmsd_close'] + for value in values: + assert ciffile[0].find_value(value), f"Missing entry {value} in CIF file" + assert (job / "RESTRAINTS.txt").is_file(), "Restraints output file not found" From 384fa1d541c0a41e354ef8b9a9c4313d5d8890cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Wed, 16 Jul 2025 11:50:18 +0100 Subject: [PATCH 05/21] dnatco report - count number of outliers and improvables --- wrappers/dnatco/script/dnatco_report.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/wrappers/dnatco/script/dnatco_report.py b/wrappers/dnatco/script/dnatco_report.py index ea3300a49..f6f8dbd69 100644 --- a/wrappers/dnatco/script/dnatco_report.py +++ b/wrappers/dnatco/script/dnatco_report.py @@ -120,6 +120,10 @@ def defaultReport(self, parent=None, ciffilePaths=[]): (compare_two and len(cif_data2['idx_outliers']) > 0) or (not compare_two and len(cif_data1['idx_outliers']) > 0) ): + n_outliers = len(cif_data2['idx_outliers']) if compare_two else len(cif_data1['idx_outliers']) + noteDiv.append( + f"{n_outliers} dinucleotide outliers reported.
" + ) table = outliersFold.addTable(title="Dinucleotides outliers") table.addData(title="Step ID", data=cif_data1['step_id_outliers']) table.addData(title="Chain", data=cif_data1['chain_display_outliers']) @@ -145,7 +149,7 @@ def defaultReport(self, parent=None, ciffilePaths=[]): improvablesFold = parent.addFold(label="Improvable dinucleotide outliers", initiallyOpen=True) noteDiv = improvablesFold.addDiv(style='font-size:110%;') noteDiv.append( - "List of unassigned dinucleotide steps that are considered" + " List of unassigned dinucleotide steps that are considered" " sufficiently close to a representative from the Golden Set. Closeness criterion is defined as RMSD" " value less or equal to 0.5 Å." ) @@ -153,6 +157,10 @@ def defaultReport(self, parent=None, ciffilePaths=[]): (compare_two and len(cif_data2['idx_improvables']) > 0) or (not compare_two and len(cif_data1['idx_improvables']) > 0) ): + n_improvables = len(cif_data2['idx_improvables']) if compare_two else len(cif_data1['idx_improvables']) + noteDiv.append( + f"{n_improvables} improvable dinucleotide outliers reported.
" + ) table = improvablesFold.addTable(title="Improvable dinucleotide outliers") table.addData(title="Step ID", data=cif_data1['step_id_improvables']) table.addData(title="Chain", data=cif_data1['chain_display_improvables']) @@ -322,9 +330,12 @@ def read_data_from_cif(self, ciffilePath, parent=None): cif_data['idx_outliers'] = [] for i, (ntc, rmsd) in enumerate(zip(cif_data['assigned_NtC'], cif_data['rmsd'])): if ntc == 'NANT': - if rmsd not in ('.', '?') and rmsd.isnumeric() and float(rmsd) <= 0.5: - cif_data['idx_improvables'].append(i) - else: + try: + if rmsd not in ('.', '?') and float(rmsd) <= 0.5: + cif_data['idx_improvables'].append(i) + else: + cif_data['idx_outliers'].append(i) + except (ValueError, TypeError): cif_data['idx_outliers'].append(i) cif_data['step_id_improvables'] = [cif_data['step_id'][i] for i in cif_data['idx_improvables']] cif_data['chain_display_improvables'] = [cif_data['chain_display'][i] for i in cif_data['idx_improvables']] From b01236d8e6e31d04df5fbfa1ae135c9e458d1c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Mon, 1 Sep 2025 17:24:10 +0100 Subject: [PATCH 06/21] fix label for MeanIo MeanIc plot --- wrappers/servalcat/script/servalcat_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/servalcat/script/servalcat_report.py b/wrappers/servalcat/script/servalcat_report.py index d3fa29e99..dd0c5e56f 100644 --- a/wrappers/servalcat/script/servalcat_report.py +++ b/wrappers/servalcat/script/servalcat_report.py @@ -566,8 +566,8 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): graphMnIoIcTitle = "Mean Io and mean Ic" graphMnIoIc = gallery.addFlotGraph( xmlnode=xmlnode, - title=graphDtitle, - internalId=graphDtitle, + title=graphMnIoIcTitle, + internalId=graphMnIoIcTitle, outputXml=self.outputXml, label=graphMnIoIcTitle, style=galleryGraphStyle) From f8af9555e7015cda366080b364f36edbfb8bb8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Mon, 1 Sep 2025 20:44:11 +0100 Subject: [PATCH 07/21] find data for MnD0FC0 MnD1FCbulk plots in binned and ml (servalcat 0.4.118) --- wrappers/servalcat/script/servalcat.py | 20 ++++-- wrappers/servalcat/script/servalcat_report.py | 65 ++++++++++--------- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/wrappers/servalcat/script/servalcat.py b/wrappers/servalcat/script/servalcat.py index a873f29db..78cc1bb66 100644 --- a/wrappers/servalcat/script/servalcat.py +++ b/wrappers/servalcat/script/servalcat.py @@ -234,13 +234,19 @@ def processOutputFiles(self): return CPluginScript.FAILED try: jsonStats = list(json.loads(jsonText)) - # add d_max_4ssqll and d_min_4ssqll - for i in range(len(jsonStats)): - for j in range(len(jsonStats[i]["data"]["binned"])): - jsonStats[i]["data"]["binned"][j]['d_min_4ssqll'] = \ - 1 / (list(jsonStats)[i]["data"]["binned"][j]['d_min'] * list(jsonStats)[i]["data"]["binned"][j]['d_min']) - jsonStats[i]["data"]["binned"][j]['d_max_4ssqll'] = \ - 1 / (list(jsonStats)[i]["data"]["binned"][j]['d_max'] * list(jsonStats)[i]["data"]["binned"][j]['d_max']) + # add d_max_4ssqll and d_min_4ssqll to 'binned' and 'ml' if present + for stat in jsonStats: + data = stat.get("data", {}) + for p in ("binned", "ml"): + if p in data: + for entry in data[p]: + d_min = entry.get('d_min') + d_max = entry.get('d_max') + # Only calculate if values are present and non-zero + if d_min: + entry['d_min_4ssqll'] = 1 / (d_min * d_min) + if d_max: + entry['d_max_4ssqll'] = 1 / (d_max * d_max) xmlText = json2xml(jsonStats, tag_name_subroot="cycle") xmlFilePath = str(os.path.join(self.getWorkDirectory(), "refined_stats.xml")) xmlText = self.xmlAddRoot(xmlText, xmlFilePath, xmlRootName="SERVALCAT") diff --git a/wrappers/servalcat/script/servalcat_report.py b/wrappers/servalcat/script/servalcat_report.py index dd0c5e56f..b566bbd50 100644 --- a/wrappers/servalcat/script/servalcat_report.py +++ b/wrappers/servalcat/script/servalcat_report.py @@ -531,39 +531,10 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): plotLine.append('colour', 'orange') plotLine.append('symbolsize', '0') - # MnD0FC0, MnD1FCbulk - only for servalcat_xtal_norefmac - if len(xmlnode.findall('.//cycle[last()]/data/binned/MnD0FC0')) > 0 and \ - len(xmlnode.findall('.//cycle[last()]/data/binned/MnD1FCbulk')) > 0: - graphDtitle = "Mean |D0*FC0| and |D1*FCbulk|" - graphD = gallery.addFlotGraph( - xmlnode=xmlnode, - title=graphDtitle, - internalId=graphDtitle, - outputXml=self.outputXml, - label=graphDtitle, - style=galleryGraphStyle) - graphD.addData(title="Resolution(Å)", select=".//cycle[last()]/data/binned/./d_min_4ssqll") - graphD.addData(title="Mean|D0*FC0|", select=".//cycle[last()]/data/binned/./MnD0FC0") - graphD.addData(title="Mean|D1*FCbulk|", select=".//cycle[last()]/data/binned/./MnD1FCbulk") - plotD = graphD.addPlotObject() - plotD.append('title', graphDtitle) - plotD.append('plottype', 'xy') - plotD.append('xlabel', 'Resolution (Å)') - plotD.append('xscale', 'oneoversqrt') - plotD.append('yrange', min=0.0) - plotD.append('legendposition', x=1, y=1) - plotLine = plotD.append('plotline', xcol=1, ycol=2) - plotLine.append('colour', 'blue') - plotLine.append('symbolsize', '0') - plotD.append('yrange', rightaxis='true') - plotLine = plotD.append('plotline', xcol=1, ycol=3, rightaxis='true') - plotLine.append('colour', 'red') - plotLine.append('symbolsize', '0') - # MnIo, MnIc - only for servalcat_xtal_norefmac if len(xmlnode.findall('.//cycle[last()]/data/binned/MnIo')) > 0 and \ len(xmlnode.findall('.//cycle[last()]/data/binned/MnIc')) > 0: - graphMnIoIcTitle = "Mean Io and mean Ic" + graphMnIoIcTitle = "Mean Io and Ic" graphMnIoIc = gallery.addFlotGraph( xmlnode=xmlnode, title=graphMnIoIcTitle, @@ -588,6 +559,40 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): plotLine.append('colour', 'red') plotLine.append('symbolsize', '0') + # MnD0FC0, MnD1FCbulk - only for servalcat_xtal_norefmac + MnD_parent = "" + MnD_parents = ["binned", "ml"] + for p in MnD_parents: + if len(xmlnode.findall(f'.//cycle[last()]/data/{p}/MnD0FC0')) > 0 and \ + len(xmlnode.findall(f'.//cycle[last()]/data/{p}/MnD1FCbulk')) > 0: + MnD_parent = p + if MnD_parent: + graphDtitle = "Mean |D0*FC0| and |D1*FCbulk|" + graphD = gallery.addFlotGraph( + xmlnode=xmlnode, + title=graphDtitle, + internalId=graphDtitle, + outputXml=self.outputXml, + label=graphDtitle, + style=galleryGraphStyle) + graphD.addData(title="Resolution(Å)", select=f".//cycle[last()]/data/{MnD_parent}/./d_min_4ssqll") + graphD.addData(title="Mean|D0*FC0|", select=f".//cycle[last()]/data/{MnD_parent}/./MnD0FC0") + graphD.addData(title="Mean|D1*FCbulk|", select=f".//cycle[last()]/data/{MnD_parent}/./MnD1FCbulk") + plotD = graphD.addPlotObject() + plotD.append('title', graphDtitle) + plotD.append('plottype', 'xy') + plotD.append('xlabel', 'Resolution (Å)') + plotD.append('xscale', 'oneoversqrt') + plotD.append('yrange', min=0.0) + plotD.append('legendposition', x=1, y=1) + plotLine = plotD.append('plotline', xcol=1, ycol=2) + plotLine.append('colour', 'blue') + plotLine.append('symbolsize', '0') + plotD.append('yrange', rightaxis='true') + plotLine = plotD.append('plotline', xcol=1, ycol=3, rightaxis='true') + plotLine.append('colour', 'red') + plotLine.append('symbolsize', '0') + clearingDiv = parent.addDiv(style="clear:both;") def addOutlierAnalysis(self, parent=None, xmlnode=None): From fce74ef273b659158eb40a2b24a0b58b4adeaf09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Mon, 1 Sep 2025 20:51:45 +0100 Subject: [PATCH 08/21] legend positions in servalcat report --- wrappers/servalcat/script/servalcat_report.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wrappers/servalcat/script/servalcat_report.py b/wrappers/servalcat/script/servalcat_report.py index b566bbd50..83f3cbd32 100644 --- a/wrappers/servalcat/script/servalcat_report.py +++ b/wrappers/servalcat/script/servalcat_report.py @@ -117,7 +117,7 @@ def addGraphPerCycle(self, parent=None, xmlnode=None): plotCC.append('ylabel', '⟨FSCmodel⟩') plotCC.append('yrange', min=0.0, max=1.0) plotCC.append('xintegral', 'true') - plotCC.append('legendposition', x=0, y=1) + plotCC.append('legendposition', x=0, y=0) plotLine = plotCC.append('plotline', xcol=1, ycol=3) plotLine.append('colour', 'orange') plotLine.append('symbolsize', '0') @@ -134,7 +134,7 @@ def addGraphPerCycle(self, parent=None, xmlnode=None): plotLL.append('xlabel', 'Cycle') plotLL.append('ylabel', '-LL') plotLL.append('xintegral', 'true') - plotLL.append('legendposition', x=0, y=1) + plotLL.append('legendposition', x=0, y=0) plotLine = plotLL.append('plotline', xcol=1, ycol=2) plotLine.append('colour', 'blue') plotLine.append('symbolsize', '0') @@ -163,7 +163,7 @@ def addGraphPerCycle(self, parent=None, xmlnode=None): plotRmsd.append('ylabel', ' ') plotRmsd.append('yrange', min=0.0) plotRmsd.append('xintegral', 'true') - plotRmsd.append('legendposition', x=0, y=0) + plotRmsd.append('legendposition', x=1, y=1) plotLine = plotRmsd.append('plotline', xcol=1, ycol=2, rightaxis='false') plotLine.append('colour', 'blue') plotLine.append('symbolsize', '0') @@ -178,7 +178,7 @@ def addGraphPerCycle(self, parent=None, xmlnode=None): plotRmsz.append('ylabel', '') plotRmsz.append('yrange', min=0.0) plotRmsz.append('xintegral', 'true') - plotRmsz.append('legendposition', x=0, y=0) + plotRmsz.append('legendposition', x=1, y=1) plotLine = plotRmsz.append('plotline', xcol=1, ycol=4, rightaxis='false') plotLine.append('colour', 'blue') plotLine.append('symbolsize', '0') @@ -524,7 +524,7 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): plotCmpl.append('title', graphCmplTitle) plotCmpl.append('plottype', 'xy') plotCmpl.append('xlabel', 'Resolution (Å)') - plotCmpl.append('legendposition', x=0, y=1) + plotCmpl.append('legendposition', x=0, y=0) plotCmpl.append('xscale', 'oneoversqrt') plotCmpl.append('yrange', min=0.0, max=100.0) plotLine = plotCmpl.append('plotline', xcol=1, ycol=2) @@ -1275,7 +1275,7 @@ def addCorrelationProgress(progressGraph): plot.append('ylabel', 'Correlation') plot.append('yrange', min=0.0, max=1.0) plot.append('xintegral', 'true') - plot.append('legendposition', x=0, y=1) + plot.append('legendposition', x=0, y=0) line = plot.append('plotline', xcol=1, ycol=4) line.append('colour', 'orange') line.append('symbolsize', '0') From 8348d1fbd8e8a43fdd761ee10228f1cf2bf2b37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Mon, 1 Sep 2025 20:55:04 +0100 Subject: [PATCH 09/21] Revert "dnatco report - count number of outliers and improvables" This reverts commit 384fa1d541c0a41e354ef8b9a9c4313d5d8890cd. --- wrappers/dnatco/script/dnatco_report.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/wrappers/dnatco/script/dnatco_report.py b/wrappers/dnatco/script/dnatco_report.py index f6f8dbd69..ea3300a49 100644 --- a/wrappers/dnatco/script/dnatco_report.py +++ b/wrappers/dnatco/script/dnatco_report.py @@ -120,10 +120,6 @@ def defaultReport(self, parent=None, ciffilePaths=[]): (compare_two and len(cif_data2['idx_outliers']) > 0) or (not compare_two and len(cif_data1['idx_outliers']) > 0) ): - n_outliers = len(cif_data2['idx_outliers']) if compare_two else len(cif_data1['idx_outliers']) - noteDiv.append( - f"{n_outliers} dinucleotide outliers reported.
" - ) table = outliersFold.addTable(title="Dinucleotides outliers") table.addData(title="Step ID", data=cif_data1['step_id_outliers']) table.addData(title="Chain", data=cif_data1['chain_display_outliers']) @@ -149,7 +145,7 @@ def defaultReport(self, parent=None, ciffilePaths=[]): improvablesFold = parent.addFold(label="Improvable dinucleotide outliers", initiallyOpen=True) noteDiv = improvablesFold.addDiv(style='font-size:110%;') noteDiv.append( - " List of unassigned dinucleotide steps that are considered" + "List of unassigned dinucleotide steps that are considered" " sufficiently close to a representative from the Golden Set. Closeness criterion is defined as RMSD" " value less or equal to 0.5 Å." ) @@ -157,10 +153,6 @@ def defaultReport(self, parent=None, ciffilePaths=[]): (compare_two and len(cif_data2['idx_improvables']) > 0) or (not compare_two and len(cif_data1['idx_improvables']) > 0) ): - n_improvables = len(cif_data2['idx_improvables']) if compare_two else len(cif_data1['idx_improvables']) - noteDiv.append( - f"{n_improvables} improvable dinucleotide outliers reported.
" - ) table = improvablesFold.addTable(title="Improvable dinucleotide outliers") table.addData(title="Step ID", data=cif_data1['step_id_improvables']) table.addData(title="Chain", data=cif_data1['chain_display_improvables']) @@ -330,12 +322,9 @@ def read_data_from_cif(self, ciffilePath, parent=None): cif_data['idx_outliers'] = [] for i, (ntc, rmsd) in enumerate(zip(cif_data['assigned_NtC'], cif_data['rmsd'])): if ntc == 'NANT': - try: - if rmsd not in ('.', '?') and float(rmsd) <= 0.5: - cif_data['idx_improvables'].append(i) - else: - cif_data['idx_outliers'].append(i) - except (ValueError, TypeError): + if rmsd not in ('.', '?') and rmsd.isnumeric() and float(rmsd) <= 0.5: + cif_data['idx_improvables'].append(i) + else: cif_data['idx_outliers'].append(i) cif_data['step_id_improvables'] = [cif_data['step_id'][i] for i in cif_data['idx_improvables']] cif_data['chain_display_improvables'] = [cif_data['chain_display'][i] for i in cif_data['idx_improvables']] From 4e9e6afba858dde3730444c98dc0e2b748c898f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Mon, 1 Sep 2025 20:55:25 +0100 Subject: [PATCH 10/21] Revert "dnatco i2run tests" This reverts commit 2a70a99df11481750849126b198ec6991db14e72. --- test/i2run/test_dnatco.py | 43 --------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 test/i2run/test_dnatco.py diff --git a/test/i2run/test_dnatco.py b/test/i2run/test_dnatco.py deleted file mode 100644 index e1f6f18e6..000000000 --- a/test/i2run/test_dnatco.py +++ /dev/null @@ -1,43 +0,0 @@ -from pathlib import Path -import gemmi -from .urls import rcsb_mmcif -from .utils import download, i2run - - -def test_rRNA(): - with download(rcsb_mmcif("1q93")) as mmcif_1q93: - args = ["dnatco_pipe"] - args += ["--XYZIN1", mmcif_1q93] - args += ["--GENERATE_RESTRAINTS", "True"] - with i2run(args) as job: - check_output(job) - - -def test_2rRNA(): - with download(rcsb_mmcif("1q93")) as mmcif_1q93, download(rcsb_mmcif("1q96")) as mmcif_1q96: - args = ["dnatco_pipe"] - args += ["--XYZIN1", mmcif_1q93] - args += ["--TOGGLE_XYZIN2", "True"] - args += ["--XYZIN2", mmcif_1q96] - args += ["--GENERATE_RESTRAINTS", "True"] - args += ["--MAX_RMSD", "0.49"] - args += ["--RESTRAINTS_SIGMA", "0.9"] - with i2run(args) as job: - check_output(job, nCiffiles=1) - - -def check_output(job: Path, nCiffiles=1): - assert (job / "RESTRAINTS.txt").is_file(), "Restraints output file not found" - for i in range(nCiffiles): - ciffilePath = job / f"CIFOUT{i + 1}.pdb" - assert ciffilePath.is_file(), "Extended CIF output file not found" - gemmi.read_structure(str(ciffilePath), format=gemmi.CoorFormat.Mmcif) - ciffile = gemmi.cif.read_file(str(ciffilePath)) - values = ['_ndb_struct_ntc_overall.confal_score', - '_ndb_struct_ntc_overall.confal_percentile', - '_ndb_struct_ntc_overall.num_classified', - '_ndb_struct_ntc_overall.num_unclassified', - '_ndb_struct_ntc_overall.num_unclassified_rmsd_close'] - for value in values: - assert ciffile[0].find_value(value), f"Missing entry {value} in CIF file" - assert (job / "RESTRAINTS.txt").is_file(), "Restraints output file not found" From 8ca1da0b205b33d890912c11b816e148df74eab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Mon, 1 Sep 2025 20:55:40 +0100 Subject: [PATCH 11/21] Revert "hide CANA in the report" This reverts commit 07ebfe30663eac51f937d847fbe0114ba1e84cd9. --- wrappers/dnatco/script/dnatco_report.py | 38 ++++++++++++------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/wrappers/dnatco/script/dnatco_report.py b/wrappers/dnatco/script/dnatco_report.py index ea3300a49..592ed59d5 100644 --- a/wrappers/dnatco/script/dnatco_report.py +++ b/wrappers/dnatco/script/dnatco_report.py @@ -109,11 +109,9 @@ def defaultReport(self, parent=None, ciffilePaths=[]): outliersFold = parent.addFold(label="Dinucleotides outliers", initiallyOpen=True) noteDiv = outliersFold.addDiv(style='font-size:110%;') noteDiv.append( - "List all unassigned dinucleotide steps. Dinucleotide conformer (NtC)" - #", resp. conformational alphabet of nucleic acids (CANA) classes " - " in the table below represent the closest NtC" - #", resp. CANA class" - " that would be assigned to the given" + "List all unassigned dinucleotide steps. Dinucleotide conformer (NtC)," + " resp. conformational alphabet of nucleic acids (CANA) classes in" + " the table below represent the closest NtC, resp. CANA class that would be assigned to the given" " dinucleotide if all assignment criteria were met." ) if ( @@ -124,9 +122,9 @@ def defaultReport(self, parent=None, ciffilePaths=[]): table.addData(title="Step ID", data=cif_data1['step_id_outliers']) table.addData(title="Chain", data=cif_data1['chain_display_outliers']) table.addData(title="Step", data=cif_data1['steps_outliers']) - #table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_outliers']) - #if compare_two: - # table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_outliers']) + table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_outliers']) + if compare_two: + table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_outliers']) table.addData(title=f"Closest NtC1{model_label}", data=cif_data1['assigned_NtC_outliers']) if compare_two: table.addData(title="Closest NtC (model 2)", data=cif_data2['assigned_NtC_outliers']) @@ -157,9 +155,9 @@ def defaultReport(self, parent=None, ciffilePaths=[]): table.addData(title="Step ID", data=cif_data1['step_id_improvables']) table.addData(title="Chain", data=cif_data1['chain_display_improvables']) table.addData(title="Step", data=cif_data1['steps_improvables']) - #table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_improvables']) - #if compare_two: - # table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_improvables']) + table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_improvables']) + if compare_two: + table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_improvables']) table.addData(title=f"Closest NtC{model_label}", data=cif_data1['assigned_NtC_improvables']) if compare_two: table.addData(title="Closest NtC (model 2)", data=cif_data2['assigned_NtC_improvables']) @@ -204,9 +202,9 @@ def defaultReport(self, parent=None, ciffilePaths=[]): table.addData(title="Step ID", data=cif_data1['step_id']) table.addData(title="Chain", data=cif_data1['chain_display']) table.addData(title="Step", data=cif_data1['steps']) - #table.addData(title=f"Assigned CANA{model_label}", data=cif_data1['assigned_CANA']) - #if compare_two: - # table.addData(title="Assigned CANA (model 2)", data=cif_data2['assigned_CANA']) + table.addData(title=f"Assigned CANA{model_label}", data=cif_data1['assigned_CANA']) + if compare_two: + table.addData(title="Assigned CANA (model 2)", data=cif_data2['assigned_CANA']) table.addData(title=f"Assigned NtC{model_label}", data=cif_data1['assigned_NtC']) if compare_two: table.addData(title="Assigned NtC (model 2)", data=cif_data2['assigned_NtC']) @@ -279,7 +277,7 @@ def read_data_from_cif(self, ciffilePath, parent=None): table_step = ciffile[0].find( '_ndb_struct_ntc_step_summary.', ['step_id', - #'assigned_CANA', + 'assigned_CANA', 'assigned_NtC', 'confal_score', 'euclidean_distance_NtC_ideal', @@ -289,13 +287,13 @@ def read_data_from_cif(self, ciffilePath, parent=None): 'closest_step_golden'] ) cif_data['step_id'] = table_step.find_column('step_id') - #cif_data['assigned_CANA'] = table_step.find_column('assigned_CANA') + cif_data['assigned_CANA'] = table_step.find_column('assigned_CANA') cif_data['assigned_NtC'] = table_step.find_column('assigned_NtC') cif_data['confal_score'] = table_step.find_column('confal_score') # cif_data['euclidean_distance_NtC_ideal'] = table_step.find_column('euclidean_distance_NtC_ideal') cif_data['rmsd'] = table_step.find_column('cartesian_rmsd_closest_NtC_representative') cif_data['closest_NtC'] = table_step.find_column('closest_NtC') - #cif_data['closest_CANA'] = table_step.find_column('closest_CANA') + cif_data['closest_CANA'] = table_step.find_column('closest_CANA') cif_data['rmsd_NtC_assigned'] = [ f"{float(rmsd):.2f} ({closest_NtC})" if closest_NtC != '.' @@ -329,18 +327,18 @@ def read_data_from_cif(self, ciffilePath, parent=None): cif_data['step_id_improvables'] = [cif_data['step_id'][i] for i in cif_data['idx_improvables']] cif_data['chain_display_improvables'] = [cif_data['chain_display'][i] for i in cif_data['idx_improvables']] cif_data['steps_improvables'] = [cif_data['steps'][i] for i in cif_data['idx_improvables']] - #cif_data['assigned_CANA_improvables'] = [cif_data['closest_CANA'][i] for i in cif_data['idx_improvables']] + cif_data['assigned_CANA_improvables'] = [cif_data['closest_CANA'][i] for i in cif_data['idx_improvables']] cif_data['assigned_NtC_improvables'] = [cif_data['closest_NtC'][i] for i in cif_data['idx_improvables']] cif_data['rmsd_NtC_improvables'] = [cif_data['rmsd'][i] for i in cif_data['idx_improvables']] cif_data['step_id_outliers'] = [cif_data['step_id'][i] for i in cif_data['idx_outliers']] cif_data['chain_display_outliers'] = [cif_data['chain_display'][i] for i in cif_data['idx_outliers']] cif_data['steps_outliers'] = [cif_data['steps'][i] for i in cif_data['idx_outliers']] - #cif_data['assigned_CANA_outliers'] = [ cif_data['closest_CANA'][i] for i in cif_data['idx_outliers']] + cif_data['assigned_CANA_outliers'] = [ cif_data['closest_CANA'][i] for i in cif_data['idx_outliers']] cif_data['assigned_NtC_outliers'] = [cif_data['closest_NtC'][i] for i in cif_data['idx_outliers']] cif_data['rmsd_NtC_outliers'] = [cif_data['rmsd'][i] for i in cif_data['idx_outliers']] - #cif_data['assigned_CANA'] = [cana if cana != 'NAN' else '-' for cana in cif_data['assigned_CANA']] + cif_data['assigned_CANA'] = [cana if cana != 'NAN' else '-' for cana in cif_data['assigned_CANA']] cif_data['assigned_NtC'] = [ntc if ntc != 'NANT' else '-' for ntc in cif_data['assigned_NtC']] return cif_data From 7de5512ac296d6ffd5703b062a71b186e5482a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Mon, 1 Sep 2025 20:55:55 +0100 Subject: [PATCH 12/21] Revert "use pathlib instead of os.path, catch properly output CIFFILE paths in reports" This reverts commit 11643238902f11240dfec61bc0e9113b8b2a8da6. --- .../dnatco_pipe/script/dnatco_pipe.def.xml | 20 -------- pipelines/dnatco_pipe/script/dnatco_pipe.py | 24 ++------- .../dnatco_pipe/script/dnatco_pipe_report.py | 22 ++++---- wrappers/dnatco/script/dnatco.def.xml | 11 +++- wrappers/dnatco/script/dnatco.py | 27 ++++++---- wrappers/dnatco/script/dnatco_report.py | 51 ++++++++++--------- 6 files changed, 70 insertions(+), 85 deletions(-) diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml b/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml index 3601643c5..700489fb5 100644 --- a/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml +++ b/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml @@ -39,26 +39,6 @@
- - CPdbDataFile - - - 1 - 2 - - True - - - - CPdbDataFile - - - 1 - 2 - - True - - CRefmacRestraintsDataFile diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe.py b/pipelines/dnatco_pipe/script/dnatco_pipe.py index 8bd169472..bdf56e466 100644 --- a/pipelines/dnatco_pipe/script/dnatco_pipe.py +++ b/pipelines/dnatco_pipe/script/dnatco_pipe.py @@ -18,7 +18,7 @@ GNU Lesser General Public License for more details. """ -from pathlib import Path +import os import shutil import xml.etree.ElementTree as ET from PySide2 import QtCore @@ -46,7 +46,7 @@ def process(self): self.dnatco1.process() if ( bool(self.container.controlParameters.TOGGLE_XYZIN2) - and Path(str(self.container.inputData.XYZIN2.fullPath)).is_file() + and os.path.isfile(str(self.container.inputData.XYZIN2.fullPath)) ): self.dnatco2 = self.dnatcoCreate(self.container.inputData.XYZIN2) self.dnatco2.process() @@ -79,30 +79,12 @@ def process_finish(self): with open(self.makeFileName('PROGRAMXML'), 'w', encoding="utf-8") as programXML: CCP4Utils.writeXML(programXML, ET.tostring(xmlroot)) - - ciffile1PathJob = str(self.dnatco1.container.outputData.CIFOUT.fullPath) - ciffile1PathPipeline = str(self.container.outputData.CIFOUT1.fullPath) - if Path(ciffile1PathJob).is_file(): - shutil.copyfile(ciffile1PathJob, ciffile1PathPipeline) - self.container.outputData.CIFOUT1.annotation.set(self.dnatco1.container.outputData.CIFOUT.annotation) - if bool(self.container.controlParameters.TOGGLE_XYZIN2): - ciffile2PathJob = str(self.dnatco2.container.outputData.CIFOUT.fullPath) - ciffile2PathPipeline = str(self.container.outputData.CIFOUT2.fullPath) - if Path(ciffile2PathJob).is_file(): - shutil.copyfile(ciffile2PathJob, ciffile2PathPipeline) - self.container.outputData.CIFOUT1.annotation.set( - str(self.dnatco1.container.outputData.CIFOUT.annotation).replace("model", "model 1") - ) - self.container.outputData.CIFOUT2.annotation.set( - str(self.dnatco2.container.outputData.CIFOUT.annotation).replace("model", "model 2") - ) - if bool(self.container.controlParameters.GENERATE_RESTRAINTS): dnatco_obj = self.dnatco2 if bool(self.container.controlParameters.TOGGLE_XYZIN2) else self.dnatco1 self.container.outputData.RESTRAINTS.annotation.set(dnatco_obj.container.outputData.RESTRAINTS.annotation) restraintsPathJob = str(dnatco_obj.container.outputData.RESTRAINTS.fullPath) restraintsPathPipeline = str(self.container.outputData.RESTRAINTS.fullPath) - if Path(restraintsPathJob).is_file(): + if os.path.isfile(restraintsPathJob): shutil.copyfile(restraintsPathJob, restraintsPathPipeline) self.reportStatus(CPluginScript.SUCCEEDED) diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe_report.py b/pipelines/dnatco_pipe/script/dnatco_pipe_report.py index 74208131e..c11dee704 100644 --- a/pipelines/dnatco_pipe/script/dnatco_pipe_report.py +++ b/pipelines/dnatco_pipe/script/dnatco_pipe_report.py @@ -21,7 +21,7 @@ from report.CCP4ReportParser import * from core import CCP4Modules from wrappers.dnatco.script.dnatco_report import dnatco_report -from pathlib import Path +import os class dnatco_pipe_report(Report): @@ -37,7 +37,7 @@ def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): jobNumber = self.jobInfo.get("jobnumber", None) jobId = self.jobInfo.get("jobid", None) jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId=jobId) - self.jobLog = str(Path(jobDirectory) / "log.txt") + self.jobLog = os.path.join(jobDirectory, "log.txt") if jobStatus is not None and jobStatus.lower() == "running": self.runningReport(parent=self) else: @@ -47,7 +47,7 @@ def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): def runningReport(self, parent=None): if parent is None: parent = self - if Path(self.jobLog).is_file(): + if os.path.isfile(self.jobLog): jobLogFold = parent.addFold(label="DNATCO log", initiallyOpen=True) jobLogFold.addPre("DNATCO is running...") @@ -57,10 +57,14 @@ def defaultReport(self, parent=None): parent = self self.addDiv(style="clear:both;") # gives space for the title - ciffile1Path = str(self.jobInfo['filenames']['CIFOUT1']) - ciffile2Path = str(self.jobInfo['filenames']['CIFOUT2']) - ciffilePaths = [ciffile1Path] - if Path(ciffile2Path).is_file(): - ciffilePaths.append(ciffile2Path) + jobId = self.jobInfo.get("jobid", None) + jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId) + + jobDirectory1 = os.path.join(jobDirectory, "job_1") # TODO: derive from jobId? + jobDirectory2 = os.path.join(jobDirectory, "job_2") # TODO: derive from jobId? + jobDirectories = [jobDirectory1] + if os.path.isdir(jobDirectory2): + jobDirectories.append(jobDirectory2) + dnatco_report1 = dnatco_report() - dnatco_report1.defaultReport(parent, ciffilePaths) \ No newline at end of file + dnatco_report1.defaultReport(parent, jobDirectories) \ No newline at end of file diff --git a/wrappers/dnatco/script/dnatco.def.xml b/wrappers/dnatco/script/dnatco.def.xml index f3bbc5112..92b6f8d7d 100644 --- a/wrappers/dnatco/script/dnatco.def.xml +++ b/wrappers/dnatco/script/dnatco.def.xml @@ -45,9 +45,18 @@ 1 2 - True + False + + CPDFDataFile + + + DNATCO report (PDF) + + False + + diff --git a/wrappers/dnatco/script/dnatco.py b/wrappers/dnatco/script/dnatco.py index bed305ca0..e9acac236 100644 --- a/wrappers/dnatco/script/dnatco.py +++ b/wrappers/dnatco/script/dnatco.py @@ -18,7 +18,7 @@ GNU Lesser General Public License for more details. """ -from pathlib import Path +import os import xml.etree.ElementTree as ET from core.CCP4PluginScript import CPluginScript from core.CCP4ErrorHandling import * @@ -70,23 +70,28 @@ def processOutputFiles(self): # outputCifFilename = outputFilenamePrefix + '_extended.cif' # outputCifPath = os.path.normpath(os.path.join(self.getWorkDirectory(), outputCifFilename)) - work_dir = Path(self.workDirectory) - cif_files = list(work_dir.glob("*_extended.cif")) - if not cif_files or not Path(str(cif_files[0])).is_file(): - self.appendErrorReport(202, str(cif_files[0])) + import glob + cif_files = glob.glob(os.path.join(self.workDirectory, "*_extended.cif")) + if cif_files: + outputCifPath = cif_files[0] + else: + outputCifPath = "" + if os.path.isfile(outputCifPath): + self.container.outputData.CIFOUT.setFullPath(outputCifPath) + self.container.outputData.CIFOUT.annotation.set('Extended model (mmCIF format)') + else: + self.appendErrorReport(202, outputCifPath) return CPluginScript.FAILED - self.container.outputData.CIFOUT.setFullPath(str(cif_files[0])) - self.container.outputData.CIFOUT.annotation.set('Extended model (mmCIF format)') if bool(self.container.controlParameters.GENERATE_RESTRAINTS): # outputRestraintsFilename = outputFilenamePrefix + '_restraints_refmac.txt' # outputRestraintsPath = os.path.normpath(os.path.join(self.getWorkDirectory(), outputRestraintsFilename)) - restraint_files = list(work_dir.glob("*_restraints_refmac.txt")) - if restraint_files: - outputRestraintsPath = str(restraint_files[0]) + cif_files = glob.glob(os.path.join(self.workDirectory, "*_restraints_refmac.txt")) + if cif_files: + outputRestraintsPath = cif_files[0] else: outputRestraintsPath = "" - if Path(outputRestraintsPath).is_file(): + if os.path.isfile(outputRestraintsPath): self.container.outputData.RESTRAINTS.setFullPath(outputRestraintsPath) self.container.outputData.RESTRAINTS.annotation.set('DNATCO restraints') else: diff --git a/wrappers/dnatco/script/dnatco_report.py b/wrappers/dnatco/script/dnatco_report.py index 592ed59d5..613a9926a 100644 --- a/wrappers/dnatco/script/dnatco_report.py +++ b/wrappers/dnatco/script/dnatco_report.py @@ -20,7 +20,7 @@ from report.CCP4ReportParser import * from core import CCP4Modules -from pathlib import Path +import os import gemmi @@ -37,7 +37,7 @@ def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): jobNumber = self.jobInfo.get("jobnumber", None) jobId = self.jobInfo.get("jobid", None) jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId=jobId) - self.jobLog = str(Path(jobDirectory) / "log.txt") + self.jobLog = os.path.join(jobDirectory, "log.txt") if jobStatus is not None and jobStatus.lower() == "running": self.runningReport(parent=self) else: @@ -47,32 +47,45 @@ def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): def runningReport(self, parent=None): if parent is None: parent = self - if Path(self.jobLog).is_file(): + if os.path.isfile(self.jobLog): jobLogFold = parent.addFold(label="DNATCO log", initiallyOpen=True) jobLogFold.addPre("DNATCO is running...") - def defaultReport(self, parent=None, ciffilePaths=[]): - + def defaultReport(self, parent=None, jobDirectories=[]): if parent is None: parent = self self.addDiv(style="clear:both;") # gives space for the title - if ciffilePaths: - if len(ciffilePaths) == 2: + if jobDirectories: + if len(jobDirectories) == 2: compare_two = True else: compare_two = False else: - # only one mmCIF file from this job + # only one job directory compare_two = False - if self.jobInfo: - if 'filenames' in self.jobInfo and 'CIFOUT' in self.jobInfo['filenames']: - ciffilePaths = [self.jobInfo['filenames']['CIFOUT']] - - cif_data1 = self.read_data_from_cif(ciffilePaths[0], parent) + jobId = self.jobInfo.get("jobid", None) + jobDirectories = [CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId)] + + # load data + import glob + + def get_cif_data(job_dir, parent): + cif_files = glob.glob(os.path.join(job_dir, "*_extended.cif")) + ciffile_path = cif_files[0] if cif_files else "" + if not os.path.isfile(ciffile_path): + noteDiv = parent.addDiv(style='font-size:110%;color:red;') + noteDiv.append( + f"DNATCO did not generate the extended CIF file in {job_dir}. No report can be generated.
" + " Please check the log files for more information." + ) + return None + return self.read_data_from_cif(ciffile_path) + + cif_data1 = get_cif_data(jobDirectories[0], parent) if compare_two: - cif_data2 = self.read_data_from_cif(ciffilePaths[1], parent) + cif_data2 = get_cif_data(jobDirectories[1], parent) # draw report overallFold = parent.addFold(label="Overall structure quality", initiallyOpen=True) @@ -218,15 +231,7 @@ def defaultReport(self, parent=None, ciffilePaths=[]): self.addDiv(style="clear:both;") - def read_data_from_cif(self, ciffilePath, parent=None): - if not Path(ciffilePath).is_file(): - noteDiv = parent.addDiv(style='font-size:110%;color:red;') - noteDiv.append( - f"DNATCO did not generate the extended CIF file (expected path: {ciffilePath})." - " No report could be generated.
" - " Please check the log files for more information." - ) - return None + def read_data_from_cif(self, ciffilePath): ciffile = gemmi.cif.read_file(ciffilePath) cif_data = {} From 0639ba8757f6a8149ac30f0bed1ff5def75c3b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Mon, 1 Sep 2025 20:56:22 +0100 Subject: [PATCH 13/21] Revert "dnatco validation and restraints generation task" This reverts commit 33c3eb5172dfc0af7c6d2c50540a0c03f54ee975. --- core/CachedLookups.json | 78 +--- pipelines/dnatco_pipe/__init__.py | 0 pipelines/dnatco_pipe/script/__init__.py | 0 .../dnatco_pipe/script/dnatco_pipe.def.xml | 60 --- .../script/dnatco_pipe.medline.txt | 90 ----- pipelines/dnatco_pipe/script/dnatco_pipe.py | 90 ----- .../dnatco_pipe/script/dnatco_pipe_gui.py | 76 ---- .../dnatco_pipe/script/dnatco_pipe_report.py | 70 ---- .../script/servalcat_pipe.def.xml | 74 ++-- .../servalcat_pipe/script/servalcat_pipe.py | 51 +-- .../script/servalcat_pipe_gui.py | 60 +-- .../script/servalcat_pipe_report.py | 20 +- qticons/CachedPixmapPaths.json | 2 +- qticons/dnatco_pipe.png | Bin 6881 -> 0 bytes qticons/dnatco_pipe_96.png | Bin 12634 -> 0 bytes wrappers/dnatco/__init__.py | 0 wrappers/dnatco/script/__init__.py | 0 wrappers/dnatco/script/dnatco.def.xml | 89 ----- wrappers/dnatco/script/dnatco.medline.txt | 90 ----- wrappers/dnatco/script/dnatco.py | 107 ------ wrappers/dnatco/script/dnatco_report.py | 349 ------------------ wrappers/servalcat/script/servalcat.def.xml | 10 - wrappers/servalcat/script/servalcat.py | 13 +- wrappers/servalcat/script/servalcat_report.py | 19 - 24 files changed, 83 insertions(+), 1265 deletions(-) delete mode 100644 pipelines/dnatco_pipe/__init__.py delete mode 100644 pipelines/dnatco_pipe/script/__init__.py delete mode 100644 pipelines/dnatco_pipe/script/dnatco_pipe.def.xml delete mode 100644 pipelines/dnatco_pipe/script/dnatco_pipe.medline.txt delete mode 100644 pipelines/dnatco_pipe/script/dnatco_pipe.py delete mode 100644 pipelines/dnatco_pipe/script/dnatco_pipe_gui.py delete mode 100644 pipelines/dnatco_pipe/script/dnatco_pipe_report.py delete mode 100644 qticons/dnatco_pipe.png delete mode 100644 qticons/dnatco_pipe_96.png delete mode 100644 wrappers/dnatco/__init__.py delete mode 100644 wrappers/dnatco/script/__init__.py delete mode 100644 wrappers/dnatco/script/dnatco.def.xml delete mode 100644 wrappers/dnatco/script/dnatco.medline.txt delete mode 100644 wrappers/dnatco/script/dnatco.py delete mode 100644 wrappers/dnatco/script/dnatco_report.py diff --git a/core/CachedLookups.json b/core/CachedLookups.json index 090bcfc5f..b35a9924c 100644 --- a/core/CachedLookups.json +++ b/core/CachedLookups.json @@ -141,9 +141,6 @@ "morda_i2", "phaser_singleMR" ], - "pipelines": [ - "dnatco_pipe" - ], "preferences": [ "guipreferences" ], @@ -160,7 +157,6 @@ "pdb_redo_api", "sheetbend", "zanuda", - "dnatco_pipe", "phaser_rnp_pipeline" ], "test": [ @@ -226,7 +222,6 @@ "buccaneer_mr", "cmapcoeff", "cpatterson", - "dnatco", "import_mosflm", "metalCoord", "prosmart", @@ -995,28 +990,6 @@ "modes": [] } }, - "dnatco": { - "0.0.0": { - "MAINTAINER": "Nobody", - "class": "wrappers.dnatco.script.dnatco_reportdnatco_report", - "clsModule": "wrappers.dnatco.script.dnatco_report", - "clsName": "dnatco_report", - "modes": [ - "Running" - ] - } - }, - "dnatco_pipe": { - "0.0.0": { - "MAINTAINER": "Nobody", - "class": "pipelines.dnatco_pipe.script.dnatco_pipe_reportdnatco_pipe_report", - "clsModule": "pipelines.dnatco_pipe.script.dnatco_pipe_report", - "clsName": "dnatco_pipe_report", - "modes": [ - "Running" - ] - } - }, "dr_mr_modelbuild_pipeline": { "0.0.0": { "MAINTAINER": "Nobody", @@ -2850,32 +2823,6 @@ "subTasks": [] } }, - "dnatco": { - "0.1": { - "DESCRIPTION": null, - "INTERRUPTLABEL": null, - "MAINTAINER": "martin.maly@mrc-lmb.cam.ac.uk", - "TASKTITLE": null, - "class": "wrappers.dnatco.script.dnatcodnatco", - "clsModule": "wrappers.dnatco.script.dnatco", - "clsName": "dnatco", - "internal": false, - "subTasks": [] - } - }, - "dnatco_pipe": { - "0.1": { - "DESCRIPTION": null, - "INTERRUPTLABEL": null, - "MAINTAINER": "martin.maly@mrc-lmb.cam.ac.uk", - "TASKTITLE": null, - "class": "pipelines.dnatco_pipe.script.dnatco_pipednatco_pipe", - "clsModule": "pipelines.dnatco_pipe.script.dnatco_pipe", - "clsName": "dnatco_pipe", - "internal": false, - "subTasks": [] - } - }, "dr_mr_modelbuild_pipeline": { "null": { "DESCRIPTION": null, @@ -3124,7 +3071,7 @@ } }, "metalCoord": { - "0.2": { + "0.1": { "DESCRIPTION": null, "INTERRUPTLABEL": null, "MAINTAINER": "martin.maly@mrc-lmb.cam.ac.uk", @@ -3886,7 +3833,7 @@ "DESCRIPTION": null, "INTERRUPTLABEL": null, "MAINTAINER": "martin.maly@mrc-lmb.cam.ac.uk", - "TASKTITLE": "Refinement against diffraction data or SPA map & optional restraints from ProSMART, MetalCoord and DNATCO", + "TASKTITLE": "Refinement against diffraction data or SPA map & optional restraints from ProSMART & MetalCoord", "class": "pipelines.servalcat_pipe.script.servalcat_pipeservalcat_pipe", "clsModule": "pipelines.servalcat_pipe.script.servalcat_pipe", "clsName": "servalcat_pipe", @@ -3894,8 +3841,7 @@ "subTasks": [ "servalcat", "prosmart", - "metalCoord", - "dnatco_pipe" + "metalCoord" ] } }, @@ -4227,7 +4173,6 @@ "coot_script_lines": "qticons/coot_script_lines.png", "coot_stepped_refine": "qticons/ccp4.png", "cpatterson": "qticons/cpatterson.png", - "cphasematch": "qticons/cphasematch.png", "crank2": "qticons/crank2.png", "crank2_comb_phdmmb": "qticons/ccp4.png", "crank2_dmfull": "qticons/ccp4.png", @@ -4249,7 +4194,6 @@ "developer_tools": "qticons/developer_tools.png", "dials_image": "qticons/dials_image.png", "dials_rlattice": "qticons/dials_rlattice.png", - "dnatco_pipe": "qticons/dnatco_pipe.png", "dr_mr_modelbuild_pipeline": "qticons/ccp4.png", "dui": "qticons/dui.png", "dummy": "qticons/ccp4.png", @@ -5547,20 +5491,6 @@ "taskVersion": 0.0 } }, - "dnatco_pipe": { - "0.0.0": { - "DESCRIPTION": "Restrain and validate nucleic acid structures", - "MAINTAINER": "Nobody", - "RANK": null, - "TASKTITLE": "DNATCO", - "class": "pipelines.dnatco_pipe.script.dnatco_pipe_guidnatco_pipe_gui", - "clsModule": "pipelines.dnatco_pipe.script.dnatco_pipe_gui", - "clsName": "dnatco_pipe_gui", - "shortTitle": "DNATCO", - "taskName": "dnatco_pipe", - "taskVersion": 0.1 - } - }, "dr_mr_modelbuild_pipeline": { "0.0.0": { "DESCRIPTION": "Data Reduction, MR, Model build pipeline", @@ -5824,7 +5754,7 @@ "clsName": "CmetalCoord_gui", "shortTitle": "MetalCoord", "taskName": "metalCoord", - "taskVersion": 0.2 + "taskVersion": 1.0 } }, "modelcraft": { diff --git a/pipelines/dnatco_pipe/__init__.py b/pipelines/dnatco_pipe/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/pipelines/dnatco_pipe/script/__init__.py b/pipelines/dnatco_pipe/script/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml b/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml deleted file mode 100644 index 700489fb5..000000000 --- a/pipelines/dnatco_pipe/script/dnatco_pipe.def.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - DEF - martinmaly - 13:15 27/Jun/25 - - 0.1 - dnatco_pipe - DNATCO - Restrain and validate nucleic acid structures - - - - - - - CCP4I2_TOP - wrappers/dnatco/script - dnatco.def.xml - - - - - - CPdbDataFile - - True - False - True - - - - CPdbDataFile - - True - True - - - - - - - CRefmacRestraintsDataFile - - - - - - - - CBoolean - - False - Compare with another structure model - - - - - - diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe.medline.txt b/pipelines/dnatco_pipe/script/dnatco_pipe.medline.txt deleted file mode 100644 index 891537ae4..000000000 --- a/pipelines/dnatco_pipe/script/dnatco_pipe.medline.txt +++ /dev/null @@ -1,90 +0,0 @@ -PMID- 32406923 -OWN - NLM -STAT- MEDLINE -DCOM- 20200908 -LR - 20240329 -IS - 1362-4962 (Electronic) -IS - 0305-1048 (Print) -IS - 0305-1048 (Linking) -VI - 48 -IP - 11 -DP - 2020 Jun 19 -TI - A unified dinucleotide alphabet describing both RNA and DNA structures. -PG - 6367-6381 -LID - 10.1093/nar/gkaa383 [doi] -AB - By analyzing almost 120 000 dinucleotides in over 2000 nonredundant nucleic acid - crystal structures, we define 96+1 diNucleotide Conformers, NtCs, which describe - the geometry of RNA and DNA dinucleotides. NtC classes are grouped into 15 codes - of the structural alphabet CANA (Conformational Alphabet of Nucleic Acids) to - simplify symbolic annotation of the prominent structural features of NAs and - their intuitive graphical display. The search for nontrivial patterns of NtCs - resulted in the identification of several types of RNA loops, some of them - observed for the first time. Over 30% of the nearly six million dinucleotides in - the PDB cannot be assigned to any NtC class but we demonstrate that up to a half - of them can be re-refined with the help of proper refinement targets. A - statistical analysis of the preferences of NtCs and CANA codes for the 16 - dinucleotide sequences showed that neither the NtC class AA00, which forms the - scaffold of RNA structures, nor BB00, the DNA most populated class, are sequence - neutral but their distributions are significantly biased. The reported automated - assignment of the NtC classes and CANA codes available at dnatco.org provides a - powerful tool for unbiased analysis of nucleic acid structures by structural and - molecular biologists. -CI - © The Author(s) 2020. Published by Oxford University Press on behalf of Nucleic - Acids Research. -FAU - Černý, Jiří -AU - Černý J -AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 - Vestec, Prague-West, Czech Republic. -FAU - Božíková, Paulína -AU - Božíková P -AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 - Vestec, Prague-West, Czech Republic. -FAU - Svoboda, Jakub -AU - Svoboda J -AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 - Vestec, Prague-West, Czech Republic. -FAU - Schneider, Bohdan -AU - Schneider B -AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 - Vestec, Prague-West, Czech Republic. -LA - eng -PT - Journal Article -PT - Research Support, Non-U.S. Gov't -PL - England -TA - Nucleic Acids Res -JT - Nucleic acids research -JID - 0411011 -RN - 0 (Nucleotides) -RN - 0 (RNA, Catalytic) -RN - 0 (Riboswitch) -RN - 63231-63-0 (RNA) -RN - 9007-49-2 (DNA) -SB - IM -MH - Binding Sites -MH - Biocatalysis -MH - DNA/*chemistry/*classification -MH - *Nucleic Acid Conformation -MH - *Nucleotide Motifs -MH - Nucleotides/*chemistry/*classification -MH - RNA/*chemistry/*classification -MH - RNA, Catalytic/chemistry/metabolism -MH - Reproducibility of Results -MH - Ribosomes/chemistry/metabolism -MH - Riboswitch -PMC - PMC7293047 -EDAT- 2020/05/15 06:00 -MHDA- 2020/09/09 06:00 -PMCR- 2020/05/14 -CRDT- 2020/05/15 06:00 -PHST- 2020/04/30 00:00 [accepted] -PHST- 2020/04/11 00:00 [revised] -PHST- 2020/04/11 00:00 [received] -PHST- 2020/05/15 06:00 [pubmed] -PHST- 2020/09/09 06:00 [medline] -PHST- 2020/05/15 06:00 [entrez] -PHST- 2020/05/14 00:00 [pmc-release] -AID - 5837055 [pii] -AID - gkaa383 [pii] -AID - 10.1093/nar/gkaa383 [doi] -PST - ppublish -SO - Nucleic Acids Res. 2020 Jun 19;48(11):6367-6381. doi: 10.1093/nar/gkaa383. diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe.py b/pipelines/dnatco_pipe/script/dnatco_pipe.py deleted file mode 100644 index bdf56e466..000000000 --- a/pipelines/dnatco_pipe/script/dnatco_pipe.py +++ /dev/null @@ -1,90 +0,0 @@ -""" - dnatco_pipe.py: CCP4 GUI Project - Copyright (C) 2025 MRC-LMB - Author: Martin Maly - - This library is free software: you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public License - version 3, modified in accordance with the provisions of the - license to address the requirements of UK law. - - You should have received a copy of the modified GNU Lesser General - Public License along with this library. If not, copies may be - downloaded from http://www.ccp4.ac.uk/ccp4license.php - - 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 Lesser General Public License for more details. -""" - -import os -import shutil -import xml.etree.ElementTree as ET -from PySide2 import QtCore -from core.CCP4PluginScript import CPluginScript -from core.CCP4ErrorHandling import * -from core import CCP4Utils - - -class dnatco_pipe(CPluginScript): - - TASKMODULE = 'pipelines' # Where this plugin will appear on gui - TASKNAME = 'dnatco_pipe' # Task name - should be same as class name - TASKVERSION = 0.1 # Version of this plugin - MAINTAINER = 'martin.maly@mrc-lmb.cam.ac.uk' - - ERROR_CODES = { 201 : { 'description' : 'No output restraint file from DNATCO' }, - 202 : { 'description' : 'No output extended mmCIF file from DNATCO' }, - 203 : { 'description' : 'No output PDF report file from DNATCO' }, - 204 : { 'description' : 'Failed to dnatcoify structure' }, - } - - - def process(self): - self.dnatco1 = self.dnatcoCreate(self.container.inputData.XYZIN1) - self.dnatco1.process() - if ( - bool(self.container.controlParameters.TOGGLE_XYZIN2) - and os.path.isfile(str(self.container.inputData.XYZIN2.fullPath)) - ): - self.dnatco2 = self.dnatcoCreate(self.container.inputData.XYZIN2) - self.dnatco2.process() - self.process_finish() - - - def dnatcoCreate(self, model): - dnatco = self.makePluginObject('dnatco') - dnatco.container.inputData.XYZIN.set(model) - dnatco.container.controlParameters.copyData(self.container.controlParameters) - # dnatco.container.controlParameters.GENERATE_RESTRAINTS.set(self.container.controlParameters.GENERATE_RESTRAINTS) - # dnatco.container.controlParameters.MAX_RMSD.set(self.container.controlParameters.MAX_RMSD) - # dnatco.container.controlParameters.RESTRAINTS_SIGMA.set(self.container.controlParameters.RESTRAINTS_SIGMA) - self.connectSignal(dnatco, 'finished', self.dnatcoFinished) - dnatco.waitForFinished = -1 - return dnatco - - - @QtCore.Slot(dict) - def dnatcoFinished(self, statusDict): - status = statusDict['finishStatus'] - if status == CPluginScript.FAILED: - self.reportStatus(status) - - - def process_finish(self): - xmlText = "" - xmlroot = ET.fromstringlist(["", xmlText, ""]) - ET.indent(xmlroot, space="\t", level=0) - with open(self.makeFileName('PROGRAMXML'), 'w', encoding="utf-8") as programXML: - CCP4Utils.writeXML(programXML, ET.tostring(xmlroot)) - - if bool(self.container.controlParameters.GENERATE_RESTRAINTS): - dnatco_obj = self.dnatco2 if bool(self.container.controlParameters.TOGGLE_XYZIN2) else self.dnatco1 - self.container.outputData.RESTRAINTS.annotation.set(dnatco_obj.container.outputData.RESTRAINTS.annotation) - restraintsPathJob = str(dnatco_obj.container.outputData.RESTRAINTS.fullPath) - restraintsPathPipeline = str(self.container.outputData.RESTRAINTS.fullPath) - if os.path.isfile(restraintsPathJob): - shutil.copyfile(restraintsPathJob, restraintsPathPipeline) - - self.reportStatus(CPluginScript.SUCCEEDED) diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe_gui.py b/pipelines/dnatco_pipe/script/dnatco_pipe_gui.py deleted file mode 100644 index dd7dc5725..000000000 --- a/pipelines/dnatco_pipe/script/dnatco_pipe_gui.py +++ /dev/null @@ -1,76 +0,0 @@ -""" - dnatco_pipe_gui.py: CCP4 GUI Project - Copyright (C) 2025 MRC-LMB - Author: Martin Maly - - This library is free software: you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public License - version 3, modified in accordance with the provisions of the - license to address the requirements of UK law. - - You should have received a copy of the modified GNU Lesser General - Public License along with this library. If not, copies may be - downloaded from http://www.ccp4.ac.uk/ccp4license.php - - 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 Lesser General Public License for more details. -""" - -from qtgui.CCP4TaskWidget import CTaskWidget - -#------------------------------------------------------------------- -class dnatco_pipe_gui(CTaskWidget): -#------------------------------------------------------------------- - - TASKNAME = 'dnatco_pipe' - TASKVERSION = 0.1 - TASKMODULE = ['refinement'] - TASKTITLE = 'DNATCO' - SHORTTASKTITLE = 'DNATCO' - DESCRIPTION = 'Restrain and validate nucleic acid structures' - WHATNEXT = ['servalcat_pipe'] - - def __init__(self,parent): - CTaskWidget.__init__(self,parent) - - def drawContents(self): - - # self.setProgramHelpFile('dnatco_pipe') - - self.openFolder(folderFunction="inputData") - - self.openSubFrame(frame=[True], title="Input data") - self.autoGenerate( - self.container.inputData, - selection={"includeParameters": ["XYZIN1"]}, - ) - self.openSubFrame(frame=[True], toggle=['controlParameters.TOGGLE_XYZIN2', 'open', [False]]) - self.autoGenerate( - self.container.controlParameters, - selection={"includeParameters": ["GENERATE_RESTRAINTS"]}, - ) - self.closeSubFrame() - self.closeSubFrame() - - self.createLine(['widget', 'TOGGLE_XYZIN2', 'label', 'Compare with another structure model']) - self.openSubFrame(frame=[True], toggle=['controlParameters.TOGGLE_XYZIN2', 'open', [True]]) - self.autoGenerate( - self.container.inputData, - selection={"includeParameters": ["XYZIN2"]}, - ) - self.autoGenerate( - self.container.controlParameters, - selection={"includeParameters": ["GENERATE_RESTRAINTS"]}, - ) - self.closeSubFrame() - - self.openSubFrame(frame=[True], title="Parameters for restraints generation", toggle=['controlParameters.GENERATE_RESTRAINTS', 'open', [True]]) - self.autoGenerate( - self.container.controlParameters, - selection={"includeParameters": ["MAX_RMSD", "RESTRAINTS_SIGMA"]}, - ) - self.closeSubFrame() - self.closeFolder() - diff --git a/pipelines/dnatco_pipe/script/dnatco_pipe_report.py b/pipelines/dnatco_pipe/script/dnatco_pipe_report.py deleted file mode 100644 index c11dee704..000000000 --- a/pipelines/dnatco_pipe/script/dnatco_pipe_report.py +++ /dev/null @@ -1,70 +0,0 @@ -""" - dnatco_pipe_report.py: CCP4 GUI Project - Copyright (C) 2025 MRC-LMB - Author: Martin Maly - - This library is free software: you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public License - version 3, modified in accordance with the provisions of the - license to address the requirements of UK law. - - You should have received a copy of the modified GNU Lesser General - Public License along with this library. If not, copies may be - downloaded from http://www.ccp4.ac.uk/ccp4license.php - - 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 Lesser General Public License for more details. -""" - -from report.CCP4ReportParser import * -from core import CCP4Modules -from wrappers.dnatco.script.dnatco_report import dnatco_report -import os - - -class dnatco_pipe_report(Report): - TASKNAME= 'dnatco_pipe' - RUNNING = True - - def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): - Report.__init__(self, xmlnode=xmlnode, jobInfo=jobInfo, jobStatus=jobStatus, **kw) - - if jobStatus is None or jobStatus.lower() == 'nooutput': return - - projectid = self.jobInfo.get("projectid", None) - jobNumber = self.jobInfo.get("jobnumber", None) - jobId = self.jobInfo.get("jobid", None) - jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId=jobId) - self.jobLog = os.path.join(jobDirectory, "log.txt") - if jobStatus is not None and jobStatus.lower() == "running": - self.runningReport(parent=self) - else: - self.defaultReport(parent=self) - - - def runningReport(self, parent=None): - if parent is None: - parent = self - if os.path.isfile(self.jobLog): - jobLogFold = parent.addFold(label="DNATCO log", initiallyOpen=True) - jobLogFold.addPre("DNATCO is running...") - - - def defaultReport(self, parent=None): - if parent is None: - parent = self - self.addDiv(style="clear:both;") # gives space for the title - - jobId = self.jobInfo.get("jobid", None) - jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId) - - jobDirectory1 = os.path.join(jobDirectory, "job_1") # TODO: derive from jobId? - jobDirectory2 = os.path.join(jobDirectory, "job_2") # TODO: derive from jobId? - jobDirectories = [jobDirectory1] - if os.path.isdir(jobDirectory2): - jobDirectories.append(jobDirectory2) - - dnatco_report1 = dnatco_report() - dnatco_report1.defaultReport(parent, jobDirectories) \ No newline at end of file diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml b/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml index 0232ad4b1..395f370c2 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml +++ b/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml @@ -460,46 +460,76 @@
- - + + + CString + + False + True + DISABLED,SELECTED,UNSELECTED,NONUCLEICACID,RIGIDMODE + DISABLED + + + CBoolean False - - + + + CString + + False + True + ALL,MANUAL + all,specify restraint types + ALL + + + + CBoolean + + False + + + CBoolean False + + + CString + + # Replace this with optional additional libg keywords + + + + + + + CBoolean + + False + CString False True - DISABLED,SELECTED,UNSELECTED,NONUCLEICACID,RIGIDMODE - DISABLED + ZN,NA_MG + Zinc,Zinc/Sodium/Magnesium + ZN - - CFloat - - 0.5 - Maximum allowed NtC RMSD (in angstroem) - Maximum allowed NtC RMSD (in angstroem) - - - - CFloat - - 1.0 - Restraints sigma factor - Restraints sigma factor - + + CBoolean + + True + - CBoolean diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe.py b/pipelines/servalcat_pipe/script/servalcat_pipe.py index 59bbbdd0e..bd9832275 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe.py +++ b/pipelines/servalcat_pipe/script/servalcat_pipe.py @@ -34,7 +34,7 @@ class servalcat_pipe(CPluginScript): TASKMODULE = 'refinement' SHORTTASKTITLE = 'Servalcat' - TASKTITLE = 'Refinement against diffraction data or SPA map & optional restraints from ProSMART, MetalCoord and DNATCO' + TASKTITLE = 'Refinement against diffraction data or SPA map & optional restraints from ProSMART & MetalCoord' TASKNAME = 'servalcat_pipe' # Task name - same as class name MAINTAINER = 'martin.maly@mrc-lmb.cam.ac.uk' TASKVERSION= 0.1 @@ -43,7 +43,7 @@ class servalcat_pipe(CPluginScript): TIMEOUT_PERIOD = 240 MAXNJOBS = 4 PERFORMANCECLASS = 'CServalcatPerformance' - SUBTASKS=['servalcat','prosmart','metalCoord','dnatco_pipe'] + SUBTASKS=['servalcat','prosmart','metalCoord'] RUNEXTERNALPROCESS=False PURGESEARCHLIST = [[ 'refmac%*/hklout.mtz', 0, "hklout" ], [ 'refmac%*/hklout.mtz', 7, "hklout" ], [ '*%*/ANOMFPHIOUT.mtz', 1, "ANOMFPHIOUT" ], [ '*%*/DIFANOMFPHIOUT.mtz', 1, "DIFANOMFPHIOUT" ]] @@ -66,12 +66,6 @@ def startProcess(self, processId): sys.stderr.write("ERROR while running ProSMART: " + str(e) + "\n") self.reportStatus(CPluginScript.FAILED) return CPluginScript.FAILED - try: - self.executeDnatcoRestraints() - except Exception as e: - sys.stderr.write("ERROR while running DNATCO restraints generation: " + str(e) + "\n") - self.reportStatus(CPluginScript.FAILED) - return CPluginScript.FAILED try: self.executeMetalCoords() except Exception as e: @@ -141,44 +135,6 @@ def prosmartNucleicAcidFinished(self, statusDict): if status == CPluginScript.FAILED: self.reportStatus(status) - def executeDnatcoRestraints(self): - # DNATCO job for restraints generation before refinement - if bool(self.container.dnatco.TOGGLE_RESTRAINTS): - self.dnatco = self.makePluginObject('dnatco') - self.dnatco.container.inputData.XYZIN.set(self.container.inputData.XYZIN) - self.dnatco.container.controlParameters.GENERATE_RESTRAINTS.set(True) - self.dnatco.container.controlParameters.MAX_RMSD.set(self.container.dnatco.MAX_RMSD) - self.dnatco.container.controlParameters.RESTRAINTS_SIGMA.set(self.container.dnatco.RESTRAINTS_SIGMA) - self.connectSignal(self.dnatco, 'finished', self.dnatcoRestraintsFinished) - self.dnatco.waitForFinished = -1 - self.dnatco.process() - - @QtCore.Slot(dict) - def dnatcoRestraintsFinished(self, statusDict): - status = statusDict['finishStatus'] - if status == CPluginScript.FAILED: - self.reportStatus(status) - - def executeDnatcoValidation(self): - # DNATCO job for structure validation - structure models before and after refinement - # Needs to be fixed - return - if bool(self.container.dnatco.TOGGLE_VALIDATION): - self.dnatcoValidation = self.makePluginObject('dnatco_pipe') - self.dnatcoValidation.container.inputData.XYZIN1.set(self.container.inputData.XYZIN) - self.dnatcoValidation.container.inputData.XYZIN2.set(self.container.outputData.CIFFILE) - self.dnatcoValidation.container.controlParameters.GENERATE_RESTRAINTS.set(False) - self.dnatcoValidation.container.controlParameters.TOGGLE_XYZIN2.set(True) - self.connectSignal(self.dnatcoValidation, 'finished', self.dnatcoValidationFinished) - self.dnatcoValidation.waitForFinished = -1 - self.dnatcoValidation.process() - - @QtCore.Slot(dict) - def dnatcoValidationFinished(self, statusDict): - status = statusDict['finishStatus'] - if status == CPluginScript.FAILED: - self.reportStatus(status) - def executeMetalCoords(self): if self.container.metalCoordPipeline.RUN_METALCOORD and \ self.container.metalCoordPipeline.GENERATE_OR_USE == "GENERATE": @@ -336,8 +292,6 @@ def createServalcatJob(self, withWeight=-1, inputCoordinates=None, ncyc=-1): # else report error? else: result.container.inputData.METALCOORD_RESTRAINTS=self.container.metalCoordPipeline.METALCOORD_RESTRAINTS - if self.container.dnatco.TOGGLE_RESTRAINTS: - result.container.inputData.DNATCO_RESTRAINTS=self.dnatco.container.outputData.RESTRAINTS if self.container.prosmartProtein.TOGGLE: result.container.controlParameters.PROSMART_PROTEIN_SGMN=self.container.prosmartProtein.SGMN result.container.controlParameters.PROSMART_PROTEIN_SGMX=self.container.prosmartProtein.SGMX @@ -843,7 +797,6 @@ def finishUp(self, servalcatJob): cleanup.purgeJob(self.servalcatPostCootPlugin.jobId,context="extended_intermediate",reportMode="skip") self.multimericValidation() - self.executeDnatcoValidation() if self.container.controlParameters.RUN_ADP_ANALYSIS: self.adp_analysis( str(self.container.outputData.CIFFILE.fullPath), diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py b/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py index 049f9c31c..107de48fb 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py +++ b/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py @@ -17,6 +17,12 @@ GNU Lesser General Public License for more details. """ +""" + Andrey Lebedev September 2011 - refmac_martin gui + Liz Potterton Aug 2012 - convert for MTZ ADO's demo + Liz Potterton Oct 2012 - Moved mini-MTZ version to refmac_martin +""" + from PySide2 import QtWidgets,QtCore from qtgui import CCP4TaskWidget from core import CCP4XtalData @@ -165,7 +171,7 @@ def drawContents(self): self.drawAdvanced() # small change introduced to allow for automatically loading a keyword file in the 'advanced' tab self.setProsmartProteinMode() self.setProsmartNucleicAcidMode() - self.setDnatcoMode() + self.setLibgMode() return def twinHelpPressed(self): @@ -313,36 +319,6 @@ def drawRestraints( self ): self.createLine( [ 'label', 'Not available.' ] ) self.closeSubFrame() - self.createLine( [ 'subtitle', 'DNATCO External Restraints for Nucleic Acids'] ) - if self.isEditable(): - self.container.dnatco.TOGGLE_RESTRAINTS.dataChanged.connect(self.setDnatcoMode) - self.container.controlParameters.REFINEMENT_MODE.dataChanged.connect(self.setDnatcoMode) - self.container.controlParameters.UNRESTRAINED.dataChanged.connect(self.setDnatcoMode) - self.container.controlParameters.FIX_XYZ.dataChanged.connect(self.setDnatcoMode) - self.container.controlParameters.JELLY_ONLY.dataChanged.connect(self.setDnatcoMode) - self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'DISABLED' ] ] ) - self.createLine( [ 'label', 'Specify atomic model before setting up external restraints' ] ) - self.closeSubFrame() - self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'NONUCLEICACID' ] ] ) - self.createLine( [ 'label', 'Input atomic model contains no nucleotide chains' ] ) - self.closeSubFrame() - self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'RIGIDMODE' ] ] ) - self.createLine( [ 'label', 'Not available in Rigid Body mode.' ] ) - self.closeSubFrame() - self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'NOTUSED' ] ] ) - self.createLine( [ 'label', 'Not available.' ] ) - self.closeSubFrame() - self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'UNSELECTED' ] ] ) - self.createLine( [ 'widget', 'dnatco.TOGGLE_RESTRAINTS', 'label', 'Generate and apply restraints for nucleic acids (experimental)' ] ) - self.closeSubFrame() - self.openSubFrame(frame=[True], toggle = ['dnatco.MODE', 'open', [ 'SELECTED' ] ] ) - self.createLine( [ 'widget', 'dnatco.TOGGLE_RESTRAINTS', 'label', 'Generate and apply restraints for nucleic acids (experimental):' ] ) - self.autoGenerate( - self.container.dnatco, - selection={"includeParameters": ["MAX_RMSD", "RESTRAINTS_SIGMA"]}, - ) - self.closeSubFrame() - self.createLine( [ 'subtitle', 'ProSMART External Restraints for Protein Chains'] ) if self.isEditable(): self.container.prosmartProtein.TOGGLE.dataChanged.connect(self.setProsmartProteinMode) @@ -443,6 +419,9 @@ def hideProsmartProteinBfac(self): def hideProsmartNucleicAcidBfac(self): return self.container.prosmartNucleicAcid.ADVANCED and not self.container.prosmartNucleicAcid.TOGGLE_BFAC + + def showLibgOptions(self): + return str(self.container.libg.OPTION) == 'MANUAL' def drawAdvanced( self ): indent = '      ' @@ -481,7 +460,6 @@ def drawAdvanced( self ): self.createLine( [ 'widget', 'VALIDATE_IRIS', 'label', 'Generate Iris validation report' ] ) self.createLine( [ 'widget', 'VALIDATE_RAMACHANDRAN', 'label', 'Generate Ramachandran plots' ] ) self.createLine( [ 'widget', 'VALIDATE_MOLPROBITY', 'label', 'Run MolProbity to analyse geometry' ] ) - self.createLine( [ 'widget', 'dnatco.TOGGLE_VALIDATION', 'label', 'Run DNATCO to validate nucleic acids' ] ) self.createLine( [ 'widget', 'RUN_ADP_ANALYSIS', 'label', 'Run ADP analysis' ] ) self.createLine( [ 'label', 'Atoms with a B-value lower than the first quartile - factor * interquartile_range
or higher than the third quartile + factor * interquartile_range to be reported. Factor:', @@ -542,7 +520,7 @@ def modelChanged(self): self.updateViewFromModel() self.setProsmartProteinMode() self.setProsmartNucleicAcidMode() - self.setDnatcoMode() + self.setLibgMode() self.getMonomersWithMetals() @QtCore.Slot() @@ -594,23 +572,21 @@ def setProsmartNucleicAcidMode(self): self.validate() @QtCore.Slot() - def setDnatcoMode(self): + def setLibgMode(self): if self.ToggleRigidModeOn(): - self.container.dnatco.MODE.set('RIGIDMODE') + self.container.libg.MODE.set('RIGIDMODE') elif self.container.inputData.XYZIN.isSet(): if self.container.inputData.NUCLEOTIDE_CHAINS.isSet(): if self.container.inputData.NUCLEOTIDE_CHAINS: - if self.container.dnatco.TOGGLE_RESTRAINTS: - self.container.dnatco.MODE.set('SELECTED') + if self.container.libg.TOGGLE: + self.container.libg.MODE.set('SELECTED') else: - self.container.dnatco.MODE.set('UNSELECTED') - self.container.dnatco.TOGGLE_VALIDATION.set(True) + self.container.libg.MODE.set('UNSELECTED') self.validate() return - self.container.dnatco.MODE.set('NONUCLEICACID') - self.container.dnatco.TOGGLE_VALIDATION.set(False) + self.container.libg.MODE.set('NONUCLEICACID') else: - self.container.dnatco.MODE.set('DISABLED') + self.container.libg.MODE.set('DISABLED') self.validate() def getMonomersWithMetals(self): diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe_report.py b/pipelines/servalcat_pipe/script/servalcat_pipe_report.py index 57ffe4de0..1c8401a5a 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe_report.py +++ b/pipelines/servalcat_pipe/script/servalcat_pipe_report.py @@ -1,28 +1,10 @@ -""" - servalcat_pipe_report.py: CCP4 GUI Project - Copyright (C) 2024 University of Southampton, MRC LMB Cambridge - - This library is free software: you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public License - version 3, modified in accordance with the provisions of the - license to address the requirements of UK law. - - You should have received a copy of the modified GNU Lesser General - Public License along with this library. If not, copies may be - downloaded from http://www.ccp4.ac.uk/ccp4license.php - - 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 Lesser General Public License for more details. -""" - import sys from xml.etree import ElementTree as ET from report.CCP4ReportParser import * from wrappers.servalcat.script import servalcat_report from wrappers.validate_protein.script import validate_protein_report +import base64 class servalcat_pipe_report(Report): diff --git a/qticons/CachedPixmapPaths.json b/qticons/CachedPixmapPaths.json index 2b3a93f50..d8cb3a24f 100644 --- a/qticons/CachedPixmapPaths.json +++ b/qticons/CachedPixmapPaths.json @@ -1 +1 @@ -{"density_calculator": "density_calculator.png", "TestObsConversions_96": "TestObsConversions_96.png", "SeqDataFile": "SeqDataFile.png", "aimless": "aimless.png", "bullet_arrow_down": "bullet_arrow_down.png", "PrepareDeposit": "PrepareDeposit.png", "pisapipe": "pisapipe.png", "MakeProjectsAndDoLigandPipeline": "MakeProjectsAndDoLigandPipeline.png", "import_xia2": "import_xia2.png", "acorn": "acorn.png", "project": "project.png", "add_fractional_coords": "add_fractional_coords.png", "help": "help.png", "density_modification_96": "density_modification_96.png", "refinement": "refinement.png", "LidiaAcedrgNew_96": "LidiaAcedrgNew_96.png", "clone": "clone.png", "validate_protein_96": "validate_protein_96.png", "ShelxCECompareHands": "ShelxCECompareHands.png", "ccp4mg_edit_model": "ccp4mg_edit_model.png", "molrep_den": "molrep_den.png", "buster": "buster.png", "file": "file.png", "model_data_utility_96": "model_data_utility_96.png", "xia2_ssx_reduce": "xia2_ssx_reduce.png", "fileopen_96": "fileopen_96.png", "help-highlight_96": "help-highlight_96.png", "new_project": "new_project.png", "book_96": "book_96.png", "phaser_pipeline_96": "phaser_pipeline_96.png", "aimless_pipe_96": "aimless_pipe_96.png", "mrparse": "mrparse.png", "toc-plus": "toc-plus.png", "validate_protein": "validate_protein.png", "newtab_96": "newtab_96.png", "ProvideSequence": "ProvideSequence.png", "ccp4i2": "ccp4i2.png", "mergeMtz": "mergeMtz.png", "undo_96": "undo_96.png", "FreeRDataFile": "FreeRDataFile.png", "back": "back.png", "parrot_96": "parrot_96.png", "List_grey": "List_grey.png", "coot_script_lines_96": "coot_script_lines_96.png", "phaser_simple": "phaser_simple.png", "pisapipe_96": "pisapipe_96.png", "density_modification": "density_modification.png", "developer_tools": "developer_tools.png", "lorestr_i2": "lorestr_i2.png", "redarrowrightshadow": "redarrowrightshadow.png", "import_mosflm": "import_mosflm.png", "MakeLink_96": "MakeLink_96.png", "xia2_dials_96": "xia2_dials_96.png", "export_arrow_new": "export_arrow_new.png", "PdbDataFile": "PdbDataFile.png", "ccp4mg_edit_nomrbump_96": "ccp4mg_edit_nomrbump_96.png", "rosette": "rosette.png", "tab-close-highlight": "tab-close-highlight.png", "molrep_pipe_96": "molrep_pipe_96.png", "UnmergedDataFile": "UnmergedDataFile.png", "cphasematch_96": "cphasematch_96.png", "ShelxCE_96": "ShelxCE_96.png", "SyncToDjango": "SyncToDjango.png", "expt_data_utility": "expt_data_utility.png", "SIMBAD_96": "SIMBAD_96.png", "crank2": "crank2.png", "molrep_selfrot_96": "molrep_selfrot_96.png", "convert2mtz_96": "convert2mtz_96.png", "shelx_96": "shelx_96.png", "xia2_dials": "xia2_dials.png", "refinement_96": "refinement_96.png", "phaser_singleMR_96": "phaser_singleMR_96.png", "toc-minus": "toc-minus.png", "greenarrowsupshadow": "greenarrowsupshadow.png", "error_star": "error_star.png", "morda_i2": "morda_i2.png", "aimless_pipe": "aimless_pipe.png", "list_add_grey": "list_add_grey.png", "slicendice_96": "slicendice_96.png", "xia2_ssx_reduce_96": "xia2_ssx_reduce_96.png", "green-tick": "green-tick.png", "phaser_ensembler": "phaser_ensembler.png", "List": "List.png", "model_building": "model_building.png", "search": "search.png", "import_xia2_96": "import_xia2_96.png", "list_delete": "list_delete.png", "MTZDataFile": "MTZDataFile.png", "ResidueRange": "ResidueRange.png", "import_arrow": "import_arrow.png", "pause": "pause.png", "list_add": "list_add.png", "ShelxCE": "ShelxCE.png", "chltofom": "chltofom.png", "greenarrowsup": "greenarrowsup.png", "ccp4mg_edit_model_96": "ccp4mg_edit_model_96.png", "PrepareDeposit_96": "PrepareDeposit_96.png", "undo": "undo.png", "prosmart_refmac_96": "prosmart_refmac_96.png", "SceneDataFile": "SceneDataFile.png", "phaser_rnp_pipeline": "phaser_rnp_pipeline.png", "database": "database.png", "chltofom_96": "chltofom_96.png", "data_processing": "data_processing.png", "download": "download.png", "phaser_ensembler_96": "phaser_ensembler_96.png", "MapCoeffsDataFile": "MapCoeffsDataFile.png", "freerflag": "freerflag.png", "morda_i2_96": "morda_i2_96.png", "coordinate_selector": "coordinate_selector.png", "editbfac_96": "editbfac_96.png", "lorestr_i2_96": "lorestr_i2_96.png", "sculptor_96": "sculptor_96.png", "AlternativeImportXIA2": "AlternativeImportXIA2.png", "buccaneer_build_refine_mr_96": "buccaneer_build_refine_mr_96.png", "zanuda_96": "zanuda_96.png", "view_forward_96": "view_forward_96.png", "import_merged_96": "import_merged_96.png", "phaser_EP": "phaser_EP.png", "download_96": "download_96.png", "newtab": "newtab.png", "phaser_EP_AUTO_96": "phaser_EP_AUTO_96.png", "cphasematch": "cphasematch.png", "developer_tools_96": "developer_tools_96.png", "book": "book.png", "phaser_MR_AUTO": "phaser_MR_AUTO.png", "data_reduction": "data_reduction.png", "bullet_arrow_right": "bullet_arrow_right.png", "export": "export.png", "csymmatch_96": "csymmatch_96.png", "Mol2DataFile": "Mol2DataFile.png", "adding_stats_to_mmcif_i2": "adding_stats_to_mmcif_i2.png", "importRefnData_96": "importRefnData_96.png", "fragon": "fragon.png", "cell": "cell.png", "alpha_fold": "alpha_fold.png", "reload": "reload.png", "ProvideAsuContents": "ProvideAsuContents.png", "ccp4mg_general": "ccp4mg_general.png", "phaser_singleMR": "phaser_singleMR.png", "help-no-highlight_96": "help-no-highlight_96.png", "forward": "forward.png", "unsatisfactory": "unsatisfactory.png", "showi2run": "showi2run.png", "ctruncate_96": "ctruncate_96.png", "AsuDataFile_96": "AsuDataFile_96.png", "help_96": "help_96.png", "MiniMtzDataFile": "MiniMtzDataFile.png", "coordinate_selector_96": "coordinate_selector_96.png", "coot_find_waters": "coot_find_waters.png", "MakeMonster_96": "MakeMonster_96.png", "list_delete_grey": "list_delete_grey.png", "ctruncate": "ctruncate.png", "coot_rsr_morph": "coot_rsr_morph.png", "molrep_den_96": "molrep_den_96.png", "i2Dimple_96": "i2Dimple_96.png", "edstats_96": "edstats_96.png", "ProvideAlignment": "ProvideAlignment.png", "phaser_simple_96": "phaser_simple_96.png", "qtpisa": "qtpisa.png", "buccaneer_build_refine_EP": "buccaneer_build_refine_EP.png", "ObsDataFile": "ObsDataFile.png", "file_manager": "file_manager.png", "splitMtz": "splitMtz.png", "imosflm_96": "imosflm_96.png", "simple_xia2_96": "simple_xia2_96.png", "fragon_96": "fragon_96.png", "phaser_pipeline": "phaser_pipeline.png", "greendot": "greendot.png", "biblio": "biblio.png", "taskmenu": "taskmenu.png", "gears": "gears.png", "dials_rlattice": "dials_rlattice.png", "DataFile": "DataFile.png", "arp_warp_classic": "arp_warp_classic.png", "phaser_EP_LLG_96": "phaser_EP_LLG_96.png", "validation_96": "validation_96.png", "shelx": "shelx.png", "aimless_96": "aimless_96.png", "youtube": "youtube.png", "expt_phasing": "expt_phasing.png", "tab-close": "tab-close.png", "servalcat_pipe_96": "servalcat_pipe_96.png", "cmapcoeff": "cmapcoeff.png", "phaser_EP_AUTO": "phaser_EP_AUTO.png", "imosflm": "imosflm.png", "tableone_96": "tableone_96.png", "clustalw": "clustalw.png", "import_arrow_new": "import_arrow_new.png", "findmyseq_96": "findmyseq_96.png", "ProvideTLS_96": "ProvideTLS_96.png", "xia2_xds": "xia2_xds.png", "DictDataFile": "DictDataFile.png", "servalcat_pipe": "servalcat_pipe.png", "xia2_xds_96": "xia2_xds_96.png", "expt_phasing_96": "expt_phasing_96.png", "coot1_96": "coot1_96.png", "pdbview_edit": "pdbview_edit.png", "data_entry_96": "data_entry_96.png", "dui_96": "dui_96.png", "export_arrow_new_96": "export_arrow_new_96.png", "molecular_replacement": "molecular_replacement.png", "PhaserRFileDataFile": "PhaserRFileDataFile.png", "EnsemblePdbDataFile": "EnsemblePdbDataFile.png", "new_project_96": "new_project_96.png", "import_processed": "import_processed.png", "biblio_96": "biblio_96.png", "dials_image": "dials_image.png", "edstats": "edstats.png", "question": "question.png", "model_building_96": "model_building_96.png", "refln_data_analysis": "refln_data_analysis.png", "pointless_reindexToMatch": "pointless_reindexToMatch.png", "thought": "thought.png", "SIMBAD": "SIMBAD.png", "ShelxCD_96": "ShelxCD_96.png", "modelcraft": "modelcraft.png", "clustalw_96": "clustalw_96.png", "crank2_96": "crank2_96.png", "list_add_red": "list_add_red.png", "file_manager2": "file_manager2.png", "dnatco_pipe_96": "dnatco_pipe_96.png", "refln_data_analysis_96": "refln_data_analysis_96.png", "MakeMonster": "MakeMonster.png", "showlogfile_96": "showlogfile_96.png", "nautilus_build_refine": "nautilus_build_refine.png", "RefmacKeywordFile": "RefmacKeywordFile.png", "shelxeMR_96": "shelxeMR_96.png", "buster_96": "buster_96.png", "model_data_utility": "model_data_utility.png", "gesamt": "gesamt.png", "sculptor": "sculptor.png", "xia2_multiplex": "xia2_multiplex.png", "running_dark": "running_dark.png", "acorn_96": "acorn_96.png", "i2Dimple": "i2Dimple.png", "phaser_EP_LLG": "phaser_EP_LLG.png", "AUSPEX": "AUSPEX.png", "arcimboldo_96": "arcimboldo_96.png", "ccp4_96": "ccp4_96.png", "listLine": "listLine.png", "validation": "validation.png", "xia2_multiplex_96": "xia2_multiplex_96.png", "nautilus": "nautilus.png", "LidiaAcedrg_96": "LidiaAcedrg_96.png", "zanuda": "zanuda.png", "coot1": "coot1.png", "view_forward": "view_forward.png", "AMPLE": "AMPLE.png", "running": "running.png", "adding_stats_to_mmcif_i2_96": "adding_stats_to_mmcif_i2_96.png", "pairef_96": "pairef_96.png", "forward_96": "forward_96.png", "AMPLE_96": "AMPLE_96.png", "ccp4mg_edit_nomrbump": "ccp4mg_edit_nomrbump.png", "ProvideAlignment_96": "ProvideAlignment_96.png", "TestObsConversions": "TestObsConversions.png", "red-cross": "red-cross.png", "file_96": "file_96.png", "molecular_replacement_96": "molecular_replacement_96.png", "dnatco_pipe": "dnatco_pipe.png", "up_96": "up_96.png", "SeqAlignDataFile": "SeqAlignDataFile.png", "export_96": "export_96.png", "privateer": "privateer.png", "ProvideAsuContents_96": "ProvideAsuContents_96.png", "fileopen": "fileopen.png", "editbfac": "editbfac.png", "molrep_pipe": "molrep_pipe.png", "expt_data_utility_96": "expt_data_utility_96.png", "buccaneer_build_refine_EP_96": "buccaneer_build_refine_EP_96.png", "bioinformatics_96": "bioinformatics_96.png", "cmapcoeff_96": "cmapcoeff_96.png", "dui": "dui.png", "blank": "blank.png", "Annotation": "Annotation.png", "CootHistoryDataFile": "CootHistoryDataFile.png", "data_reduction_96": "data_reduction_96.png", "MakeLink": "MakeLink.png", "Mol2DataFile_96": "Mol2DataFile_96.png", "privateer_96": "privateer_96.png", "mrparse_96": "mrparse_96.png", "down_96": "down_96.png", "job": "job.png", "help-highlight": "help-highlight.png", "ligands": "ligands.png", "pdbview_edit_96": "pdbview_edit_96.png", "prosmart_refmac": "prosmart_refmac.png", "mrbump_basic": "mrbump_basic.png", "qtpisa_96": "qtpisa_96.png", "ccp4": "ccp4.png", "cpatterson": "cpatterson.png", "import_processed_96": "import_processed_96.png", "taskmenu_96": "taskmenu_96.png", "pointless_reindexToMatch_96": "pointless_reindexToMatch_96.png", "sad": "sad.png", "shelxeMR": "shelxeMR.png", "mergeMtz_96": "mergeMtz_96.png", "data_processing_96": "data_processing_96.png", "pdb_redo_api": "pdb_redo_api.png", "matthews_96": "matthews_96.png", "coot_script_lines": "coot_script_lines.png", "up": "up.png", "MakeProjectsAndDoLigandPipeline_96": "MakeProjectsAndDoLigandPipeline_96.png", "file_info": "file_info.png", "MDLMolDataFile": "MDLMolDataFile.png", "search-plus": "search-plus.png", "tableone": "tableone.png", "dials_rlattice_96": "dials_rlattice_96.png", "redarrowright": "redarrowright.png", "ProvideTLS": "ProvideTLS.png", "SubstituteLigand": "SubstituteLigand.png", "buccaneer_build_refine_mr": "buccaneer_build_refine_mr.png", "backgroundJobTitle": "backgroundJobTitle.png", "ShelxCD": "ShelxCD.png", "freerflag_96": "freerflag_96.png", "MTZDataFile_96": "MTZDataFile_96.png", "help-no-highlight": "help-no-highlight.png", "importRefnData": "importRefnData.png", "AlternativeImportXIA2_96": "AlternativeImportXIA2_96.png", "LidiaAcedrg": "LidiaAcedrg.png", "coot_rebuild_96": "coot_rebuild_96.png", "coot_find_waters_96": "coot_find_waters_96.png", "gesamt_96": "gesamt_96.png", "reload_96": "reload_96.png", "bioinformatics": "bioinformatics.png", "chainsaw": "chainsaw.png", "down": "down.png", "MapDataFile": "MapDataFile.png", "zoom": "zoom.png", "slicendice": "slicendice.png", "gears2": "gears2.png", "phaser_rnp_pipeline_96": "phaser_rnp_pipeline_96.png", "showlogfile": "showlogfile.png", "dustbin": "dustbin.png", "import_mosflm_96": "import_mosflm_96.png", "SubstituteLigand_96": "SubstituteLigand_96.png", "zoom_96": "zoom_96.png", "pairef": "pairef.png", "arcimboldo": "arcimboldo.png", "back_96": "back_96.png", "ShelxCECompareHands_96": "ShelxCECompareHands_96.png", "AsuDataFile": "AsuDataFile.png", "PhaserSolDataFile": "PhaserSolDataFile.png", "SyncToDjango_96": "SyncToDjango_96.png", "clone_96": "clone_96.png", "splitMtz_96": "splitMtz_96.png", "import_arrow_new_96": "import_arrow_new_96.png", "phaser_MR_AUTO_96": "phaser_MR_AUTO_96.png", "simple_xia2": "simple_xia2.png", "dials_image_96": "dials_image_96.png", "coot_rebuild": "coot_rebuild.png", "import_merged": "import_merged.png", "MDLMolDataFile_96": "MDLMolDataFile_96.png", "PhsDataFile": "PhsDataFile.png", "scaleit": "scaleit.png", "data_entry": "data_entry.png", "molrep_selfrot": "molrep_selfrot.png", "list_delete_red": "list_delete_red.png", "ligands_96": "ligands_96.png", "parrot": "parrot.png", "LidiaAcedrgNew": "LidiaAcedrgNew.png", "ProvideSequence_96": "ProvideSequence_96.png", "job_interrupted": "job_interrupted.png", "ccp4mg_general_96": "ccp4mg_general_96.png", "findmyseq": "findmyseq.png", "undone": "undone.png", "ShowList": "ShowList.png", "matthews": "matthews.png", "csymmatch": "csymmatch.png", "convert2mtz": "convert2mtz.png", "chainsaw_96": "chainsaw_96.png"} \ No newline at end of file +{"RefmacKeywordFile": "RefmacKeywordFile.png", "Mol2DataFile_96": "Mol2DataFile_96.png", "Annotation": "Annotation.png", "zanuda": "zanuda.png", "thought": "thought.png", "refinement": "refinement.png", "MakeProjectsAndDoLigandPipeline": "MakeProjectsAndDoLigandPipeline.png", "molrep_pipe": "molrep_pipe.png", "arcimboldo": "arcimboldo.png", "acorn": "acorn.png", "LidiaAcedrg_96": "LidiaAcedrg_96.png", "forward": "forward.png", "tab-close-highlight": "tab-close-highlight.png", "book_96": "book_96.png", "arcimboldo_96": "arcimboldo_96.png", "phaser_singleMR_96": "phaser_singleMR_96.png", "UnmergedDataFile": "UnmergedDataFile.png", "molrep_selfrot_96": "molrep_selfrot_96.png", "freerflag": "freerflag.png", "AsuDataFile": "AsuDataFile.png", "fileopen": "fileopen.png", "gesamt": "gesamt.png", "DictDataFile": "DictDataFile.png", "phaser_EP_AUTO": "phaser_EP_AUTO.png", "simple_xia2_96": "simple_xia2_96.png", "list_add": "list_add.png", "sculptor_96": "sculptor_96.png", "import_mosflm_96": "import_mosflm_96.png", "file_manager2": "file_manager2.png", "privateer_96": "privateer_96.png", "chainsaw_96": "chainsaw_96.png", "list_delete_grey": "list_delete_grey.png", "phaser_pipeline": "phaser_pipeline.png", "AlternativeImportXIA2_96": "AlternativeImportXIA2_96.png", "aimless_pipe": "aimless_pipe.png", "showlogfile_96": "showlogfile_96.png", "buccaneer_build_refine_mr": "buccaneer_build_refine_mr.png", "help-no-highlight": "help-no-highlight.png", "molrep_den": "molrep_den.png", "ccp4mg_edit_model_96": "ccp4mg_edit_model_96.png", "DataFile": "DataFile.png", "import_processed": "import_processed.png", "bullet_arrow_right": "bullet_arrow_right.png", "showlogfile": "showlogfile.png", "view_forward_96": "view_forward_96.png", "aimless_pipe_96": "aimless_pipe_96.png", "FreeRDataFile": "FreeRDataFile.png", "MapCoeffsDataFile": "MapCoeffsDataFile.png", "expt_data_utility_96": "expt_data_utility_96.png", "shelx": "shelx.png", "editbfac": "editbfac.png", "job_interrupted": "job_interrupted.png", "xia2_xds_96": "xia2_xds_96.png", "green-tick": "green-tick.png", "coot_rsr_morph": "coot_rsr_morph.png", "ProvideAlignment": "ProvideAlignment.png", "prosmart_refmac_96": "prosmart_refmac_96.png", "xia2_dials_96": "xia2_dials_96.png", "phaser_EP_LLG_96": "phaser_EP_LLG_96.png", "AMPLE": "AMPLE.png", "csymmatch_96": "csymmatch_96.png", "i2Dimple": "i2Dimple.png", "blank": "blank.png", "coot_script_lines_96": "coot_script_lines_96.png", "arp_warp_classic": "arp_warp_classic.png", "biblio": "biblio.png", "phaser_ensembler_96": "phaser_ensembler_96.png", "showi2run": "showi2run.png", "undo_96": "undo_96.png", "import_arrow_new_96": "import_arrow_new_96.png", "ProvideAsuContents_96": "ProvideAsuContents_96.png", "fragon": "fragon.png", "AlternativeImportXIA2": "AlternativeImportXIA2.png", "list_delete_red": "list_delete_red.png", "undone": "undone.png", "ShelxCECompareHands": "ShelxCECompareHands.png", "developer_tools": "developer_tools.png", "gesamt_96": "gesamt_96.png", "pointless_reindexToMatch_96": "pointless_reindexToMatch_96.png", "chltofom_96": "chltofom_96.png", "alpha_fold": "alpha_fold.png", "expt_phasing": "expt_phasing.png", "error_star": "error_star.png", "cphasematch": "cphasematch.png", "phaser_ensembler": "phaser_ensembler.png", "parrot": "parrot.png", "scaleit": "scaleit.png", "shelx_96": "shelx_96.png", "MakeMonster": "MakeMonster.png", "editbfac_96": "editbfac_96.png", "export_arrow_new": "export_arrow_new.png", "database": "database.png", "splitMtz": "splitMtz.png", "data_entry": "data_entry.png", "data_processing": "data_processing.png", "ProvideSequence_96": "ProvideSequence_96.png", "ShelxCD": "ShelxCD.png", "phaser_pipeline_96": "phaser_pipeline_96.png", "data_reduction_96": "data_reduction_96.png", "nautilus_build_refine": "nautilus_build_refine.png", "ProvideTLS_96": "ProvideTLS_96.png", "ccp4i2": "ccp4i2.png", "search": "search.png", "PrepareDeposit_96": "PrepareDeposit_96.png", "ShelxCE": "ShelxCE.png", "listLine": "listLine.png", "dui_96": "dui_96.png", "up_96": "up_96.png", "pdb_redo_api": "pdb_redo_api.png", "download_96": "download_96.png", "pause": "pause.png", "expt_data_utility": "expt_data_utility.png", "dials_image_96": "dials_image_96.png", "help-highlight": "help-highlight.png", "developer_tools_96": "developer_tools_96.png", "acorn_96": "acorn_96.png", "back": "back.png", "xia2_multiplex": "xia2_multiplex.png", "ShowList": "ShowList.png", "CootHistoryDataFile": "CootHistoryDataFile.png", "tableone_96": "tableone_96.png", "newtab_96": "newtab_96.png", "gears": "gears.png", "ProvideAlignment_96": "ProvideAlignment_96.png", "buster": "buster.png", "importRefnData": "importRefnData.png", "greenarrowsup": "greenarrowsup.png", "imosflm_96": "imosflm_96.png", "data_reduction": "data_reduction.png", "cphasematch_96": "cphasematch_96.png", "zoom_96": "zoom_96.png", "LidiaAcedrgNew": "LidiaAcedrgNew.png", "MTZDataFile_96": "MTZDataFile_96.png", "export": "export.png", "phaser_rnp_pipeline": "phaser_rnp_pipeline.png", "list_delete": "list_delete.png", "clone_96": "clone_96.png", "lorestr_i2": "lorestr_i2.png", "molrep_den_96": "molrep_den_96.png", "cmapcoeff": "cmapcoeff.png", "list_add_grey": "list_add_grey.png", "backgroundJobTitle": "backgroundJobTitle.png", "PhaserSolDataFile": "PhaserSolDataFile.png", "down_96": "down_96.png", "file_96": "file_96.png", "validate_protein": "validate_protein.png", "buccaneer_build_refine_EP": "buccaneer_build_refine_EP.png", "i2Dimple_96": "i2Dimple_96.png", "dustbin": "dustbin.png", "morda_i2": "morda_i2.png", "density_modification_96": "density_modification_96.png", "phaser_MR_AUTO": "phaser_MR_AUTO.png", "cpatterson": "cpatterson.png", "MakeLink": "MakeLink.png", "freerflag_96": "freerflag_96.png", "ccp4mg_edit_model": "ccp4mg_edit_model.png", "xia2_xds": "xia2_xds.png", "greenarrowsupshadow": "greenarrowsupshadow.png", "modelcraft": "modelcraft.png", "file_info": "file_info.png", "dui": "dui.png", "PhaserRFileDataFile": "PhaserRFileDataFile.png", "book": "book.png", "import_merged_96": "import_merged_96.png", "List": "List.png", "refln_data_analysis": "refln_data_analysis.png", "model_building": "model_building.png", "import_mosflm": "import_mosflm.png", "xia2_ssx_reduce_96": "xia2_ssx_reduce_96.png", "importRefnData_96": "importRefnData_96.png", "refinement_96": "refinement_96.png", "back_96": "back_96.png", "phaser_MR_AUTO_96": "phaser_MR_AUTO_96.png", "shelxeMR_96": "shelxeMR_96.png", "coordinate_selector_96": "coordinate_selector_96.png", "pairef_96": "pairef_96.png", "matthews": "matthews.png", "shelxeMR": "shelxeMR.png", "SIMBAD": "SIMBAD.png", "ProvideTLS": "ProvideTLS.png", "validation_96": "validation_96.png", "molecular_replacement": "molecular_replacement.png", "view_forward": "view_forward.png", "servalcat_pipe": "servalcat_pipe.png", "xia2_ssx_reduce": "xia2_ssx_reduce.png", "MakeLink_96": "MakeLink_96.png", "coot_script_lines": "coot_script_lines.png", "mrparse_96": "mrparse_96.png", "zanuda_96": "zanuda_96.png", "findmyseq": "findmyseq.png", "SIMBAD_96": "SIMBAD_96.png", "dials_image": "dials_image.png", "adding_stats_to_mmcif_i2": "adding_stats_to_mmcif_i2.png", "MakeProjectsAndDoLigandPipeline_96": "MakeProjectsAndDoLigandPipeline_96.png", "import_xia2": "import_xia2.png", "ObsDataFile": "ObsDataFile.png", "ProvideAsuContents": "ProvideAsuContents.png", "greendot": "greendot.png", "fileopen_96": "fileopen_96.png", "phaser_rnp_pipeline_96": "phaser_rnp_pipeline_96.png", "matthews_96": "matthews_96.png", "servalcat_pipe_96": "servalcat_pipe_96.png", "tab-close": "tab-close.png", "dials_rlattice": "dials_rlattice.png", "simple_xia2": "simple_xia2.png", "pairef": "pairef.png", "project": "project.png", "model_data_utility_96": "model_data_utility_96.png", "list_add_red": "list_add_red.png", "molecular_replacement_96": "molecular_replacement_96.png", "convert2mtz": "convert2mtz.png", "toc-plus": "toc-plus.png", "question": "question.png", "splitMtz_96": "splitMtz_96.png", "import_processed_96": "import_processed_96.png", "ctruncate": "ctruncate.png", "aimless": "aimless.png", "reload_96": "reload_96.png", "help-highlight_96": "help-highlight_96.png", "List_grey": "List_grey.png", "ctruncate_96": "ctruncate_96.png", "sad": "sad.png", "ProvideSequence": "ProvideSequence.png", "xia2_dials": "xia2_dials.png", "mergeMtz": "mergeMtz.png", "model_data_utility": "model_data_utility.png", "lorestr_i2_96": "lorestr_i2_96.png", "pisapipe_96": "pisapipe_96.png", "density_modification": "density_modification.png", "toc-minus": "toc-minus.png", "red-cross": "red-cross.png", "edstats": "edstats.png", "LidiaAcedrgNew_96": "LidiaAcedrgNew_96.png", "model_building_96": "model_building_96.png", "phaser_simple_96": "phaser_simple_96.png", "import_xia2_96": "import_xia2_96.png", "phaser_EP_AUTO_96": "phaser_EP_AUTO_96.png", "pisapipe": "pisapipe.png", "youtube": "youtube.png", "mrbump_basic": "mrbump_basic.png", "redarrowright": "redarrowright.png", "csymmatch": "csymmatch.png", "crank2": "crank2.png", "bioinformatics_96": "bioinformatics_96.png", "ccp4mg_edit_nomrbump": "ccp4mg_edit_nomrbump.png", "ResidueRange": "ResidueRange.png", "zoom": "zoom.png", "phaser_EP_LLG": "phaser_EP_LLG.png", "add_fractional_coords": "add_fractional_coords.png", "mergeMtz_96": "mergeMtz_96.png", "SyncToDjango_96": "SyncToDjango_96.png", "SeqAlignDataFile": "SeqAlignDataFile.png", "AUSPEX": "AUSPEX.png", "convert2mtz_96": "convert2mtz_96.png", "running": "running.png", "nautilus": "nautilus.png", "dials_rlattice_96": "dials_rlattice_96.png", "new_project_96": "new_project_96.png", "coot_find_waters": "coot_find_waters.png", "ShelxCE_96": "ShelxCE_96.png", "EnsemblePdbDataFile": "EnsemblePdbDataFile.png", "SubstituteLigand_96": "SubstituteLigand_96.png", "bioinformatics": "bioinformatics.png", "ShelxCD_96": "ShelxCD_96.png", "bullet_arrow_down": "bullet_arrow_down.png", "file_manager": "file_manager.png", "undo": "undo.png", "rosette": "rosette.png", "buster_96": "buster_96.png", "edstats_96": "edstats_96.png", "taskmenu_96": "taskmenu_96.png", "AMPLE_96": "AMPLE_96.png", "clone": "clone.png", "data_entry_96": "data_entry_96.png", "molrep_pipe_96": "molrep_pipe_96.png", "PrepareDeposit": "PrepareDeposit.png", "SeqDataFile": "SeqDataFile.png", "ccp4": "ccp4.png", "phaser_singleMR": "phaser_singleMR.png", "molrep_selfrot": "molrep_selfrot.png", "slicendice_96": "slicendice_96.png", "export_arrow_new_96": "export_arrow_new_96.png", "clustalw": "clustalw.png", "coot_find_waters_96": "coot_find_waters_96.png", "MakeMonster_96": "MakeMonster_96.png", "coot_rebuild": "coot_rebuild.png", "ccp4_96": "ccp4_96.png", "import_merged": "import_merged.png", "up": "up.png", "SceneDataFile": "SceneDataFile.png", "help_96": "help_96.png", "PhsDataFile": "PhsDataFile.png", "gears2": "gears2.png", "pdbview_edit": "pdbview_edit.png", "xia2_multiplex_96": "xia2_multiplex_96.png", "download": "download.png", "newtab": "newtab.png", "expt_phasing_96": "expt_phasing_96.png", "search-plus": "search-plus.png", "AsuDataFile_96": "AsuDataFile_96.png", "ShelxCECompareHands_96": "ShelxCECompareHands_96.png", "ligands": "ligands.png", "aimless_96": "aimless_96.png", "parrot_96": "parrot_96.png", "phaser_simple": "phaser_simple.png", "ccp4mg_edit_nomrbump_96": "ccp4mg_edit_nomrbump_96.png", "unsatisfactory": "unsatisfactory.png", "imosflm": "imosflm.png", "chainsaw": "chainsaw.png", "coot_rebuild_96": "coot_rebuild_96.png", "TestObsConversions": "TestObsConversions.png", "running_dark": "running_dark.png", "density_calculator": "density_calculator.png", "down": "down.png", "TestObsConversions_96": "TestObsConversions_96.png", "phaser_EP": "phaser_EP.png", "PdbDataFile": "PdbDataFile.png", "taskmenu": "taskmenu.png", "adding_stats_to_mmcif_i2_96": "adding_stats_to_mmcif_i2_96.png", "validation": "validation.png", "privateer": "privateer.png", "mrparse": "mrparse.png", "validate_protein_96": "validate_protein_96.png", "qtpisa_96": "qtpisa_96.png", "buccaneer_build_refine_mr_96": "buccaneer_build_refine_mr_96.png", "coordinate_selector": "coordinate_selector.png", "SubstituteLigand": "SubstituteLigand.png", "job": "job.png", "prosmart_refmac": "prosmart_refmac.png", "sculptor": "sculptor.png", "help-no-highlight_96": "help-no-highlight_96.png", "biblio_96": "biblio_96.png", "import_arrow_new": "import_arrow_new.png", "cell": "cell.png", "ligands_96": "ligands_96.png", "findmyseq_96": "findmyseq_96.png", "export_96": "export_96.png", "crank2_96": "crank2_96.png", "morda_i2_96": "morda_i2_96.png", "buccaneer_build_refine_EP_96": "buccaneer_build_refine_EP_96.png", "MapDataFile": "MapDataFile.png", "SyncToDjango": "SyncToDjango.png", "qtpisa": "qtpisa.png", "MTZDataFile": "MTZDataFile.png", "refln_data_analysis_96": "refln_data_analysis_96.png", "help": "help.png", "ccp4mg_general": "ccp4mg_general.png", "forward_96": "forward_96.png", "cmapcoeff_96": "cmapcoeff_96.png", "LidiaAcedrg": "LidiaAcedrg.png", "new_project": "new_project.png", "redarrowrightshadow": "redarrowrightshadow.png", "MDLMolDataFile": "MDLMolDataFile.png", "reload": "reload.png", "file": "file.png", "MiniMtzDataFile": "MiniMtzDataFile.png", "tableone": "tableone.png", "MDLMolDataFile_96": "MDLMolDataFile_96.png", "slicendice": "slicendice.png", "pdbview_edit_96": "pdbview_edit_96.png", "chltofom": "chltofom.png", "data_processing_96": "data_processing_96.png", "Mol2DataFile": "Mol2DataFile.png", "ccp4mg_general_96": "ccp4mg_general_96.png", "import_arrow": "import_arrow.png", "pointless_reindexToMatch": "pointless_reindexToMatch.png", "fragon_96": "fragon_96.png", "clustalw_96": "clustalw_96.png"} \ No newline at end of file diff --git a/qticons/dnatco_pipe.png b/qticons/dnatco_pipe.png deleted file mode 100644 index 4dfe74f150c2ecd7fc67ad95925f37349007b6d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6881 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 diff --git a/qticons/dnatco_pipe_96.png b/qticons/dnatco_pipe_96.png deleted file mode 100644 index bb94b40cb1bbe5eaa806ee2a5be1b94d99014040..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12634 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%0AehEO9e6 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 diff --git a/wrappers/dnatco/__init__.py b/wrappers/dnatco/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/wrappers/dnatco/script/__init__.py b/wrappers/dnatco/script/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/wrappers/dnatco/script/dnatco.def.xml b/wrappers/dnatco/script/dnatco.def.xml deleted file mode 100644 index 92b6f8d7d..000000000 --- a/wrappers/dnatco/script/dnatco.def.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - DEF - martinmaly - 13:15 27/Jun/25 - - 0.1 - dnatco - DNATCO - Restrain and validate nucleic acid structures - - - - - - - CPdbDataFile - - True - False - True - - - - - - - - CRefmacRestraintsDataFile - - - - - CPdbDataFile - - - 1 - 2 - - False - - - - CPDFDataFile - - - DNATCO report (PDF) - - False - - - - - - - CFloat - - 0.5 - Maximum allowed NtC RMSD (in angstroem) - Maximum allowed NtC RMSD (in angstroem) - - - - CFloat - - 1.0 - Restraints sigma factor - Restraints sigma factor - - - - CBoolean - - False - Generate restraints - - - - - - diff --git a/wrappers/dnatco/script/dnatco.medline.txt b/wrappers/dnatco/script/dnatco.medline.txt deleted file mode 100644 index 891537ae4..000000000 --- a/wrappers/dnatco/script/dnatco.medline.txt +++ /dev/null @@ -1,90 +0,0 @@ -PMID- 32406923 -OWN - NLM -STAT- MEDLINE -DCOM- 20200908 -LR - 20240329 -IS - 1362-4962 (Electronic) -IS - 0305-1048 (Print) -IS - 0305-1048 (Linking) -VI - 48 -IP - 11 -DP - 2020 Jun 19 -TI - A unified dinucleotide alphabet describing both RNA and DNA structures. -PG - 6367-6381 -LID - 10.1093/nar/gkaa383 [doi] -AB - By analyzing almost 120 000 dinucleotides in over 2000 nonredundant nucleic acid - crystal structures, we define 96+1 diNucleotide Conformers, NtCs, which describe - the geometry of RNA and DNA dinucleotides. NtC classes are grouped into 15 codes - of the structural alphabet CANA (Conformational Alphabet of Nucleic Acids) to - simplify symbolic annotation of the prominent structural features of NAs and - their intuitive graphical display. The search for nontrivial patterns of NtCs - resulted in the identification of several types of RNA loops, some of them - observed for the first time. Over 30% of the nearly six million dinucleotides in - the PDB cannot be assigned to any NtC class but we demonstrate that up to a half - of them can be re-refined with the help of proper refinement targets. A - statistical analysis of the preferences of NtCs and CANA codes for the 16 - dinucleotide sequences showed that neither the NtC class AA00, which forms the - scaffold of RNA structures, nor BB00, the DNA most populated class, are sequence - neutral but their distributions are significantly biased. The reported automated - assignment of the NtC classes and CANA codes available at dnatco.org provides a - powerful tool for unbiased analysis of nucleic acid structures by structural and - molecular biologists. -CI - © The Author(s) 2020. Published by Oxford University Press on behalf of Nucleic - Acids Research. -FAU - Černý, Jiří -AU - Černý J -AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 - Vestec, Prague-West, Czech Republic. -FAU - Božíková, Paulína -AU - Božíková P -AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 - Vestec, Prague-West, Czech Republic. -FAU - Svoboda, Jakub -AU - Svoboda J -AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 - Vestec, Prague-West, Czech Republic. -FAU - Schneider, Bohdan -AU - Schneider B -AD - Institute of Biotechnology of the Czech Academy of Sciences, BIOCEV, CZ-252 50 - Vestec, Prague-West, Czech Republic. -LA - eng -PT - Journal Article -PT - Research Support, Non-U.S. Gov't -PL - England -TA - Nucleic Acids Res -JT - Nucleic acids research -JID - 0411011 -RN - 0 (Nucleotides) -RN - 0 (RNA, Catalytic) -RN - 0 (Riboswitch) -RN - 63231-63-0 (RNA) -RN - 9007-49-2 (DNA) -SB - IM -MH - Binding Sites -MH - Biocatalysis -MH - DNA/*chemistry/*classification -MH - *Nucleic Acid Conformation -MH - *Nucleotide Motifs -MH - Nucleotides/*chemistry/*classification -MH - RNA/*chemistry/*classification -MH - RNA, Catalytic/chemistry/metabolism -MH - Reproducibility of Results -MH - Ribosomes/chemistry/metabolism -MH - Riboswitch -PMC - PMC7293047 -EDAT- 2020/05/15 06:00 -MHDA- 2020/09/09 06:00 -PMCR- 2020/05/14 -CRDT- 2020/05/15 06:00 -PHST- 2020/04/30 00:00 [accepted] -PHST- 2020/04/11 00:00 [revised] -PHST- 2020/04/11 00:00 [received] -PHST- 2020/05/15 06:00 [pubmed] -PHST- 2020/09/09 06:00 [medline] -PHST- 2020/05/15 06:00 [entrez] -PHST- 2020/05/14 00:00 [pmc-release] -AID - 5837055 [pii] -AID - gkaa383 [pii] -AID - 10.1093/nar/gkaa383 [doi] -PST - ppublish -SO - Nucleic Acids Res. 2020 Jun 19;48(11):6367-6381. doi: 10.1093/nar/gkaa383. diff --git a/wrappers/dnatco/script/dnatco.py b/wrappers/dnatco/script/dnatco.py deleted file mode 100644 index e9acac236..000000000 --- a/wrappers/dnatco/script/dnatco.py +++ /dev/null @@ -1,107 +0,0 @@ -""" - dnatco.py: CCP4 GUI Project - Copyright (C) 2025 MRC-LMB - Author: Martin Maly - - This library is free software: you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public License - version 3, modified in accordance with the provisions of the - license to address the requirements of UK law. - - You should have received a copy of the modified GNU Lesser General - Public License along with this library. If not, copies may be - downloaded from http://www.ccp4.ac.uk/ccp4license.php - - 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 Lesser General Public License for more details. -""" - -import os -import xml.etree.ElementTree as ET -from core.CCP4PluginScript import CPluginScript -from core.CCP4ErrorHandling import * -from core import CCP4Utils - - -class dnatco(CPluginScript): - - TASKMODULE = 'wrappers' # Where this plugin will appear on gui - TASKNAME = 'dnatco' # Task name - should be same as class name - TASKVERSION= 0.1 # Version of this plugin - TASKCOMMAND = '/opt/ccp4-9/bin/dnatco' # The command to run the executable - MAINTAINER = 'martin.maly@mrc-lmb.cam.ac.uk' - - ERROR_CODES = { 201 : { 'description' : 'No output restraint file from DNATCO' }, - 202 : { 'description' : 'No output extended mmCIF file from DNATCO' }, - 203 : { 'description' : 'No output PDF report file from DNATCO' }, - 204 : { 'description' : 'Failed to dnatcoify structure' }, - } - - - def makeCommandAndScript(self): - self.appendCommandLine(['--coords', str(self.container.inputData.XYZIN.fullPath)]) - self.appendCommandLine(['--outputDir', self.workDirectory]) - self.appendCommandLine(['--extendedCIF']) - # if self.container.inputData.HKLIN.isSet(): - # self.appendCommandLine(['--reflns', str(self.container.inputData.HKLIN.fullPath)]) - if bool(self.container.controlParameters.GENERATE_RESTRAINTS): - self.appendCommandLine(['--refmacRestraints']) - if float(self.container.controlParameters.MAX_RMSD) != 0.5: - self.appendCommandLine(['--restraintsRmsd', str(float(self.container.controlParameters.MAX_RMSD))]) - if float(self.container.controlParameters.RESTRAINTS_SIGMA) != 1.0: - self.appendCommandLine(['--restraintsSigmaFactor', str(float(self.container.controlParameters.RESTRAINTS_SIGMA))]) - - - def processOutputFiles(self): - # outputFilenamePrefix = str(os.path.splitext( - # os.path.basename(str( - # self.container.inputData.XYZIN.fullPath)))[0]).upper().replace("_1", "") # TODO - # outputFilenamePrefix = "1Q93" - - # check that DNATCO has finished successfully - logText = self.logFileText() - pyListLogLines = logText.split("\n") - for j, pyStrLine in enumerate(pyListLogLines): - if "Failed to dnatcoify structure" in pyStrLine: - self.appendErrorReport(204) - return CPluginScript.FAILED - - # outputCifFilename = outputFilenamePrefix + '_extended.cif' - # outputCifPath = os.path.normpath(os.path.join(self.getWorkDirectory(), outputCifFilename)) - import glob - cif_files = glob.glob(os.path.join(self.workDirectory, "*_extended.cif")) - if cif_files: - outputCifPath = cif_files[0] - else: - outputCifPath = "" - if os.path.isfile(outputCifPath): - self.container.outputData.CIFOUT.setFullPath(outputCifPath) - self.container.outputData.CIFOUT.annotation.set('Extended model (mmCIF format)') - else: - self.appendErrorReport(202, outputCifPath) - return CPluginScript.FAILED - - if bool(self.container.controlParameters.GENERATE_RESTRAINTS): - # outputRestraintsFilename = outputFilenamePrefix + '_restraints_refmac.txt' - # outputRestraintsPath = os.path.normpath(os.path.join(self.getWorkDirectory(), outputRestraintsFilename)) - cif_files = glob.glob(os.path.join(self.workDirectory, "*_restraints_refmac.txt")) - if cif_files: - outputRestraintsPath = cif_files[0] - else: - outputRestraintsPath = "" - if os.path.isfile(outputRestraintsPath): - self.container.outputData.RESTRAINTS.setFullPath(outputRestraintsPath) - self.container.outputData.RESTRAINTS.annotation.set('DNATCO restraints') - else: - self.appendErrorReport(201, outputRestraintsPath) - return CPluginScript.FAILED - - xmlText = "" - xmlroot = ET.fromstringlist(["", xmlText, ""]) - ET.indent(xmlroot, space="\t", level=0) - with open(self.makeFileName('PROGRAMXML'), 'w', encoding="utf-8") as programXML: - CCP4Utils.writeXML(programXML, ET.tostring(xmlroot)) - - return CPluginScript.SUCCEEDED diff --git a/wrappers/dnatco/script/dnatco_report.py b/wrappers/dnatco/script/dnatco_report.py deleted file mode 100644 index 613a9926a..000000000 --- a/wrappers/dnatco/script/dnatco_report.py +++ /dev/null @@ -1,349 +0,0 @@ -""" - dnatco_report.py: CCP4 GUI Project - Copyright (C) 2025 MRC-LMB - Author: Martin Maly - - This library is free software: you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public License - version 3, modified in accordance with the provisions of the - license to address the requirements of UK law. - - You should have received a copy of the modified GNU Lesser General - Public License along with this library. If not, copies may be - downloaded from http://www.ccp4.ac.uk/ccp4license.php - - 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 Lesser General Public License for more details. -""" - -from report.CCP4ReportParser import * -from core import CCP4Modules -import os -import gemmi - - -class dnatco_report(Report): - TASKNAME= 'dnatco' - RUNNING = True - - def __init__(self, xmlnode=None, jobInfo={}, jobStatus=None, **kw): - Report.__init__( self, xmlnode=xmlnode, jobInfo=jobInfo, jobStatus=jobStatus, **kw) - - if jobStatus is None or jobStatus.lower() == 'nooutput': return - - projectid = self.jobInfo.get("projectid", None) - jobNumber = self.jobInfo.get("jobnumber", None) - jobId = self.jobInfo.get("jobid", None) - jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId=jobId) - self.jobLog = os.path.join(jobDirectory, "log.txt") - if jobStatus is not None and jobStatus.lower() == "running": - self.runningReport(parent=self) - else: - self.defaultReport(parent=self) - - - def runningReport(self, parent=None): - if parent is None: - parent = self - if os.path.isfile(self.jobLog): - jobLogFold = parent.addFold(label="DNATCO log", initiallyOpen=True) - jobLogFold.addPre("DNATCO is running...") - - - def defaultReport(self, parent=None, jobDirectories=[]): - if parent is None: - parent = self - self.addDiv(style="clear:both;") # gives space for the title - - if jobDirectories: - if len(jobDirectories) == 2: - compare_two = True - else: - compare_two = False - else: - # only one job directory - compare_two = False - jobId = self.jobInfo.get("jobid", None) - jobDirectories = [CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId)] - - # load data - import glob - - def get_cif_data(job_dir, parent): - cif_files = glob.glob(os.path.join(job_dir, "*_extended.cif")) - ciffile_path = cif_files[0] if cif_files else "" - if not os.path.isfile(ciffile_path): - noteDiv = parent.addDiv(style='font-size:110%;color:red;') - noteDiv.append( - f"DNATCO did not generate the extended CIF file in {job_dir}. No report can be generated.
" - " Please check the log files for more information." - ) - return None - return self.read_data_from_cif(ciffile_path) - - cif_data1 = get_cif_data(jobDirectories[0], parent) - if compare_two: - cif_data2 = get_cif_data(jobDirectories[1], parent) - - # draw report - overallFold = parent.addFold(label="Overall structure quality", initiallyOpen=True) - noteDiv = overallFold.addDiv(style='font-size:110%;') - noteDiv.append( - "Assignment of conformer class (NtC) to dinucleotide steps and the quality of fit between each" - " dinucleotide and the NtC reference structure, measured by RMSD and confal score." - ) - table = overallFold.addTable(title="Overall structure quality", transpose=True) - if compare_two: - table.addData(title="", data=["Model 1", "Model 2"]) - table.addData(title="No. NtC assigned", data=[cif_data1['overall_num_classified'], cif_data2['overall_num_classified']]) - table.addData(title="No. NtC close", data=[cif_data1['overall_num_unclassified_rmsd_close'], cif_data2['overall_num_unclassified_rmsd_close']]) - table.addData(title="No. NtC unassigned", data=[cif_data1['overall_num_unclassified'], cif_data2['overall_num_unclassified']]) - table.addData(title="No. steps with RMSD < 0.5 Å", data=[cif_data1["overall_num_rmsd_below_0p5"], cif_data2["overall_num_rmsd_below_0p5"]]) - table.addData(title="No. steps with RMSD 0.5-1 Å", data=[cif_data1["overall_num_rmsd_between_0p5_and_1"], cif_data2["overall_num_rmsd_between_0p5_and_1"]]) - table.addData(title="No. steps with RMSD > 1 Å", data=[cif_data1["overall_num_rmsd_higher_1"], cif_data2["overall_num_rmsd_higher_1"]]) - table.addData(title="Confal score", data=[cif_data1['overall_confal_score'], cif_data2['overall_confal_score']]) - table.addData(title="Confal score percentile", data=[cif_data1['overall_confal_percentile'], cif_data2['overall_confal_percentile']]) - else: - table.addData(title="No. NtC assigned", data=[cif_data1['overall_num_classified']]) - table.addData(title="No. NtC close", data=[cif_data1['overall_num_unclassified_rmsd_close']]) - table.addData(title="No. NtC unassigned", data=[cif_data1['overall_num_unclassified']]) - table.addData(title="No. steps with RMSD < 0.5 Å", data=[cif_data1["overall_num_rmsd_below_0p5"]]) - table.addData(title="No. steps with RMSD 0.5-1 Å", data=[cif_data1["overall_num_rmsd_between_0p5_and_1"]]) - table.addData(title="No. steps with RMSD > 1 Å", data=[cif_data1["overall_num_rmsd_higher_1"]]) - table.addData(title="Confal score", data=[cif_data1['overall_confal_score']]) - table.addData(title="Confal score percentile", data=[cif_data1['overall_confal_percentile']]) - - if compare_two: - model_label = " (model 1)" - else: - model_label = "" - outliersFold = parent.addFold(label="Dinucleotides outliers", initiallyOpen=True) - noteDiv = outliersFold.addDiv(style='font-size:110%;') - noteDiv.append( - "List all unassigned dinucleotide steps. Dinucleotide conformer (NtC)," - " resp. conformational alphabet of nucleic acids (CANA) classes in" - " the table below represent the closest NtC, resp. CANA class that would be assigned to the given" - " dinucleotide if all assignment criteria were met." - ) - if ( - (compare_two and len(cif_data2['idx_outliers']) > 0) - or (not compare_two and len(cif_data1['idx_outliers']) > 0) - ): - table = outliersFold.addTable(title="Dinucleotides outliers") - table.addData(title="Step ID", data=cif_data1['step_id_outliers']) - table.addData(title="Chain", data=cif_data1['chain_display_outliers']) - table.addData(title="Step", data=cif_data1['steps_outliers']) - table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_outliers']) - if compare_two: - table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_outliers']) - table.addData(title=f"Closest NtC1{model_label}", data=cif_data1['assigned_NtC_outliers']) - if compare_two: - table.addData(title="Closest NtC (model 2)", data=cif_data2['assigned_NtC_outliers']) - table.addData(title=f"RMSD to closest NtC representative (Å){model_label}", data=cif_data1['rmsd_NtC_outliers']) - if compare_two: - table.addData(title="RMSD to closest NtC representative (Å) (model 1)", data=cif_data2['rmsd_NtC_outliers']) - noteDiv = outliersFold.addDiv(style='font-size:110%;') - noteDiv.append( - "A more detailed analysis could be performed at the" - " DNATCO web server." - ) - else: - noteDiv = outliersFold.addDiv(style='font-size:110%;') - noteDiv.append("No dinucleotide outliers found.") - - improvablesFold = parent.addFold(label="Improvable dinucleotide outliers", initiallyOpen=True) - noteDiv = improvablesFold.addDiv(style='font-size:110%;') - noteDiv.append( - "List of unassigned dinucleotide steps that are considered" - " sufficiently close to a representative from the Golden Set. Closeness criterion is defined as RMSD" - " value less or equal to 0.5 Å." - ) - if ( - (compare_two and len(cif_data2['idx_improvables']) > 0) - or (not compare_two and len(cif_data1['idx_improvables']) > 0) - ): - table = improvablesFold.addTable(title="Improvable dinucleotide outliers") - table.addData(title="Step ID", data=cif_data1['step_id_improvables']) - table.addData(title="Chain", data=cif_data1['chain_display_improvables']) - table.addData(title="Step", data=cif_data1['steps_improvables']) - table.addData(title=f"Closest CANA{model_label}", data=cif_data1['assigned_CANA_improvables']) - if compare_two: - table.addData(title="Closest CANA (model 2)", data=cif_data2['assigned_CANA_improvables']) - table.addData(title=f"Closest NtC{model_label}", data=cif_data1['assigned_NtC_improvables']) - if compare_two: - table.addData(title="Closest NtC (model 2)", data=cif_data2['assigned_NtC_improvables']) - table.addData(title=f"RMSD to closest NtC representative (Å){model_label}", data=cif_data1['rmsd_NtC_improvables']) - if compare_two: - table.addData(title="RMSD to closest NtC representative (Å) (model 1)", data=cif_data2['rmsd_NtC_improvables']) - noteDiv = improvablesFold.addDiv(style='font-size:110%;') - noteDiv.append( - "A more detailed analysis could be performed at the" - " DNATCO web server." - ) - else: - noteDiv = improvablesFold.addDiv(style='font-size:110%;') - noteDiv.append("No improvable dinucleotide outliers found.") - - allDinucleotidesFold = parent.addFold(label="All dinucleotides", initiallyOpen=True) - - graphPerStep = allDinucleotidesFold.addFlotGraph( - title="RMSD per step", - style="height:330px; width:585px; border:0px; padding:10px; padding-left:15px; margin-right:15px;") - graphPerStep.addData(title="step", data=cif_data1['step_id']) - graphPerStep.addData(title="RMSD_model1(A)", data=cif_data1['rmsd']) - if compare_two: - graphPerStep.addData(title="RMSD_model2(A)", data=cif_data2['rmsd']) - plotPerStep = graphPerStep.addPlotObject() - plotPerStep.append('title', 'RMSD per step') - plotPerStep.append('plottype', 'xy') - plotPerStep.append('xlabel', 'step id') - plotPerStep.append('ylabel', 'RMSD (A)') - plotPerStep.append('legendposition', x=1, y=0) # right bottom corner - plotLine = plotPerStep.append('plotline', xcol=1, ycol=2) - plotLine.append('symbolsize', '1') - plotLine.append('linestyle', '.') - plotLine.append('colour', 'gray') - if compare_two: - plotLine2 = plotPerStep.append('plotline', xcol=1, ycol=3) - plotLine2.append('symbolsize', '1') - plotLine2.append('linestyle', '.') - plotLine2.append('colour', 'blue') - - table = allDinucleotidesFold.addTable(title="All dinucleotides") - table.addData(title="Step ID", data=cif_data1['step_id']) - table.addData(title="Chain", data=cif_data1['chain_display']) - table.addData(title="Step", data=cif_data1['steps']) - table.addData(title=f"Assigned CANA{model_label}", data=cif_data1['assigned_CANA']) - if compare_two: - table.addData(title="Assigned CANA (model 2)", data=cif_data2['assigned_CANA']) - table.addData(title=f"Assigned NtC{model_label}", data=cif_data1['assigned_NtC']) - if compare_two: - table.addData(title="Assigned NtC (model 2)", data=cif_data2['assigned_NtC']) - table.addData(title=f"Confal score{model_label}", data=cif_data1['confal_score']) - if compare_two: - table.addData(title="Confal score (model 2)", data=cif_data2['rmsd_NtC_assigned']) - table.addData(title=f"RMSD to closest NtC representative (Å){model_label}", data=cif_data1['rmsd_NtC_assigned']) - if compare_two: - table.addData(title="RMSD to closest NtC representative (Å) (model 2)", data=cif_data2['rmsd_NtC_assigned']) - - self.addDiv(style="clear:both;") - - - def read_data_from_cif(self, ciffilePath): - ciffile = gemmi.cif.read_file(ciffilePath) - cif_data = {} - - cif_data['overall_confal_score'] = ciffile[0].find_value('_ndb_struct_ntc_overall.confal_score') - cif_data['overall_confal_percentile'] = ciffile[0].find_value('_ndb_struct_ntc_overall.confal_percentile') - cif_data['overall_num_classified'] = ciffile[0].find_value('_ndb_struct_ntc_overall.num_classified') - cif_data['overall_num_unclassified'] = ciffile[0].find_value('_ndb_struct_ntc_overall.num_unclassified') - cif_data['overall_num_unclassified_rmsd_close'] = ciffile[0].find_value( - '_ndb_struct_ntc_overall.num_unclassified_rmsd_close' - ) - - table_step_id = ciffile[0].find( - '_ndb_struct_ntc_step.', - ['id', - 'name', - 'PDB_model_number', - 'label_entity_id_1', - 'label_asym_id_1', - 'label_seq_id_1', - 'label_comp_id_1', - 'label_alt_id_1', - 'label_entity_id_2', - 'label_asym_id_2', - 'label_seq_id_2', - 'label_comp_id_2', - 'label_alt_id_2', - 'auth_asym_id_1', - 'auth_seq_id_1', - 'auth_asym_id_2', - 'auth_seq_id_2', - 'PDB_ins_code_1', - 'PDB_ins_code_2'] - ) - cif_data['chain_label'] = table_step_id.find_column('label_asym_id_1') - cif_data['chain_auth'] = table_step_id.find_column('auth_asym_id_1') - cif_data['chain_display'] = [ - f"{label}" - if label == auth - else f"{label} (auth: {auth})" for label, auth in zip(cif_data['chain_label'], cif_data['chain_auth']) - ] - cif_data['comp_id_1'] = table_step_id.find_column('label_comp_id_1') - cif_data['auth_seq_id_1'] = table_step_id.find_column('auth_seq_id_1') - cif_data['comp_id_2'] = table_step_id.find_column('label_comp_id_2') - cif_data['auth_seq_id_2'] = table_step_id.find_column('auth_seq_id_2') - cif_data['steps'] = [f"{comp_id_1}{seq_id_1} {comp_id_2}{seq_id_2}" for comp_id_1, seq_id_1, comp_id_2, seq_id_2 in zip( - cif_data['comp_id_1'], cif_data['auth_seq_id_1'], cif_data['comp_id_2'], cif_data['auth_seq_id_2'])] - - table_step = ciffile[0].find( - '_ndb_struct_ntc_step_summary.', - ['step_id', - 'assigned_CANA', - 'assigned_NtC', - 'confal_score', - 'euclidean_distance_NtC_ideal', - 'cartesian_rmsd_closest_NtC_representative', - 'closest_CANA', - 'closest_NtC', - 'closest_step_golden'] - ) - cif_data['step_id'] = table_step.find_column('step_id') - cif_data['assigned_CANA'] = table_step.find_column('assigned_CANA') - cif_data['assigned_NtC'] = table_step.find_column('assigned_NtC') - cif_data['confal_score'] = table_step.find_column('confal_score') - # cif_data['euclidean_distance_NtC_ideal'] = table_step.find_column('euclidean_distance_NtC_ideal') - cif_data['rmsd'] = table_step.find_column('cartesian_rmsd_closest_NtC_representative') - cif_data['closest_NtC'] = table_step.find_column('closest_NtC') - cif_data['closest_CANA'] = table_step.find_column('closest_CANA') - cif_data['rmsd_NtC_assigned'] = [ - f"{float(rmsd):.2f} ({closest_NtC})" - if closest_NtC != '.' - else f"{float(rmsd):.2f}" for rmsd, closest_NtC in zip(cif_data['rmsd'], cif_data['closest_NtC']) - ] - cif_data['idx_rmsd_below_0p5'] = [ - i for i, rmsd in enumerate(cif_data['rmsd']) - if rmsd not in ('.', '?') and float(rmsd) <= 0.5 - ] - cif_data['idx_rmsd_between_0p5_and_1'] = [ - i for i, rmsd in enumerate(cif_data['rmsd']) - if rmsd not in ('.', '?') and 0.5 < float(rmsd) <= 1.0 - ] - cif_data['idx_rmsd_higher_1'] = [ - i for i, rmsd in enumerate(cif_data['rmsd']) - if rmsd not in ('.', '?') and float(rmsd) > 1.0 - ] - cif_data["overall_num_rmsd_below_0p5"] = len(cif_data['idx_rmsd_below_0p5']) - cif_data["overall_num_rmsd_between_0p5_and_1"] = len(cif_data['idx_rmsd_between_0p5_and_1']) - cif_data["overall_num_rmsd_higher_1"]= len(cif_data['idx_rmsd_higher_1']) - - # Filter data based on CANA/NtC assignment and RMSD values - cif_data['idx_improvables'] = [] - cif_data['idx_outliers'] = [] - for i, (ntc, rmsd) in enumerate(zip(cif_data['assigned_NtC'], cif_data['rmsd'])): - if ntc == 'NANT': - if rmsd not in ('.', '?') and rmsd.isnumeric() and float(rmsd) <= 0.5: - cif_data['idx_improvables'].append(i) - else: - cif_data['idx_outliers'].append(i) - cif_data['step_id_improvables'] = [cif_data['step_id'][i] for i in cif_data['idx_improvables']] - cif_data['chain_display_improvables'] = [cif_data['chain_display'][i] for i in cif_data['idx_improvables']] - cif_data['steps_improvables'] = [cif_data['steps'][i] for i in cif_data['idx_improvables']] - cif_data['assigned_CANA_improvables'] = [cif_data['closest_CANA'][i] for i in cif_data['idx_improvables']] - cif_data['assigned_NtC_improvables'] = [cif_data['closest_NtC'][i] for i in cif_data['idx_improvables']] - cif_data['rmsd_NtC_improvables'] = [cif_data['rmsd'][i] for i in cif_data['idx_improvables']] - - cif_data['step_id_outliers'] = [cif_data['step_id'][i] for i in cif_data['idx_outliers']] - cif_data['chain_display_outliers'] = [cif_data['chain_display'][i] for i in cif_data['idx_outliers']] - cif_data['steps_outliers'] = [cif_data['steps'][i] for i in cif_data['idx_outliers']] - cif_data['assigned_CANA_outliers'] = [ cif_data['closest_CANA'][i] for i in cif_data['idx_outliers']] - cif_data['assigned_NtC_outliers'] = [cif_data['closest_NtC'][i] for i in cif_data['idx_outliers']] - cif_data['rmsd_NtC_outliers'] = [cif_data['rmsd'][i] for i in cif_data['idx_outliers']] - - cif_data['assigned_CANA'] = [cana if cana != 'NAN' else '-' for cana in cif_data['assigned_CANA']] - cif_data['assigned_NtC'] = [ntc if ntc != 'NANT' else '-' for ntc in cif_data['assigned_NtC']] - - return cif_data diff --git a/wrappers/servalcat/script/servalcat.def.xml b/wrappers/servalcat/script/servalcat.def.xml index 0cccfc654..cecfdc215 100644 --- a/wrappers/servalcat/script/servalcat.def.xml +++ b/wrappers/servalcat/script/servalcat.def.xml @@ -92,16 +92,6 @@ See the "Generate a Free R set" task in the "Data reduction" section. MetalCoord restraints for metal sites
- - CRefmacRestraintsDataFile - - True - True - True - True - DNATCO restraints for nucleic acids - - CRefmacRestraintsDataFile diff --git a/wrappers/servalcat/script/servalcat.py b/wrappers/servalcat/script/servalcat.py index 78cc1bb66..bd94e1993 100644 --- a/wrappers/servalcat/script/servalcat.py +++ b/wrappers/servalcat/script/servalcat.py @@ -1,6 +1,6 @@ """ servalcat.py: CCP4 GUI Project - Copyright (C) 2024 University of Southampton, MRC-LMB Cambridge + Copyright (C) 2024 MRC-LBM, University of Southampton This library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License @@ -475,7 +475,7 @@ def makeCommandAndScript(self): keywordFilePath = str(os.path.join(self.getWorkDirectory(), 'keywords.txt')) - if str(float(self.container.controlParameters.VDWR_WEIGHT)) != "1.0": + if str(float(self.container.controlParameters.VDWR_WEIGHT)) != "1": with open(keywordFilePath, "a+") as keywordFile: keywordFile.write("VDWR " + str(self.container.controlParameters.VDWR_WEIGHT) + "\n") # Occupancy refinement @@ -510,9 +510,6 @@ def makeCommandAndScript(self): if self.container.inputData.METALCOORD_RESTRAINTS.isSet(): with open(keywordFilePath, "a+") as keywordFile: keywordFile.write("\n@%s"%(str(self.container.inputData.METALCOORD_RESTRAINTS.fullPath))) - if self.container.inputData.DNATCO_RESTRAINTS.isSet(): - with open(keywordFilePath, "a+") as keywordFile: - keywordFile.write("\n@%s"%(str(self.container.inputData.DNATCO_RESTRAINTS.fullPath))) if self.container.inputData.PROSMART_PROTEIN_RESTRAINTS.isSet(): with open(keywordFilePath, "a+") as keywordFile: if self.container.controlParameters.PROSMART_PROTEIN_SGMN.isSet(): @@ -585,6 +582,6 @@ def flushXml(self): # assumes self.xmlroot and self.xmlLength are well set CCP4Utils.writeXML(programXmlFile, newXml) shutil.move(self.makeFileName('PROGRAMXML')+'_tmp', self.makeFileName('PROGRAMXML')) - """def setProgramVersion(self): - print('servalcat.getProgramVersion') - return CPluginScript.setProgramVersion(self,'servalcat')""" + def setProgramVersion(self): + print('refmac.getProgramVersion') + return CPluginScript.setProgramVersion(self,'Refmac_5') diff --git a/wrappers/servalcat/script/servalcat_report.py b/wrappers/servalcat/script/servalcat_report.py index 83f3cbd32..4bba67785 100644 --- a/wrappers/servalcat/script/servalcat_report.py +++ b/wrappers/servalcat/script/servalcat_report.py @@ -1,22 +1,3 @@ -""" - servalcat_report.py: CCP4 GUI Project - Copyright (C) 2024 University of Southampton, MRC-LMB Cambridge - - This library is free software: you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public License - version 3, modified in accordance with the provisions of the - license to address the requirements of UK law. - - You should have received a copy of the modified GNU Lesser General - Public License along with this library. If not, copies may be - downloaded from http://www.ccp4.ac.uk/ccp4license.php - - This program is distributed in the hope that it will be useful,S - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - """ - from report.CCP4ReportParser import * import sys from xml.etree import ElementTree as ET From 2cc8ec8fc185bd8a1851f21d31ffcb215f5b934e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Tue, 2 Sep 2025 09:05:42 +0100 Subject: [PATCH 14/21] plot for n_R1work, n_R1free, n_R1 in servalcat report --- wrappers/servalcat/script/servalcat_report.py | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/wrappers/servalcat/script/servalcat_report.py b/wrappers/servalcat/script/servalcat_report.py index 4bba67785..514dc580f 100644 --- a/wrappers/servalcat/script/servalcat_report.py +++ b/wrappers/servalcat/script/servalcat_report.py @@ -457,6 +457,7 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): plotLine.append('colour', 'blue') plotLine.append('symbolsize', '0') + # Number of reflections - only for servalcat_xtal_norefmac if len(xmlnode.findall('.//cycle[last()]/data/binned/n_obs')) > 0 and \ len(xmlnode.findall('.//cycle[last()]/data/binned/n_work')) > 0: graphNtitle = "Number of reflections" @@ -479,15 +480,50 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): plotN.append('legendposition', x=0, y=1) plotN.append('xscale', 'oneoversqrt') plotLine = plotN.append('plotline', xcol=1, ycol=2) - plotLine.append('colour', 'orange') + plotLine.append('colour', 'red') plotLine.append('symbolsize', '0') plotLine = plotN.append('plotline', xcol=1, ycol=3) - plotLine.append('colour', 'blue') + plotLine.append('colour', 'orange') plotLine.append('symbolsize', '0') plotN.append('yrange', rightaxis='true') plotLine = plotN.append('plotline', xcol=1, ycol=4) # , rightaxis='true') - plotLine.append('colour', 'red') + plotLine.append('colour', 'blue') + plotLine.append('symbolsize', '0') + + # Number of reflections used for R1 calculation - only for servalcat_xtal_norefmac + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1')) > 0 or \ + len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1work')) > 0 or \ + len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1free')) > 0: + graphNR1title = "Number of reflections used for R1 calculation" + graphNR1 = gallery.addFlotGraph( + xmlnode=xmlnode, + title=graphNR1title, + internalId=graphNR1title, + outputXml=self.outputXml, + label=graphNR1title, + style=galleryGraphStyle) + second_plot = False + graphNR1.addData(title="Resolution(Å)", select=".//cycle[last()]/data/binned/./d_min_4ssqll") + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1')) > 0: + graphNR1.addData(title="N_R1", select=".//cycle[last()]/data/binned/./n_R1") + elif len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1work')) > 0: + graphNR1.addData(title="N_R1work", select=".//cycle[last()]/data/binned/./n_R1work") + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1free')) > 0: + graphNR1.addData(title="N_R1free", select=".//cycle[last()]/data/binned/./n_R1free") + second_plot = True + plotNR1 = graphNR1.addPlotObject() + plotNR1.append('title', graphNR1title) + plotNR1.append('plottype', 'xy') + plotNR1.append('xlabel', 'Resolution (Å)') + plotNR1.append('legendposition', x=0, y=1) + plotNR1.append('xscale', 'oneoversqrt') + plotLine = plotNR1.append('plotline', xcol=1, ycol=2) + plotLine.append('colour', 'orange') plotLine.append('symbolsize', '0') + if second_plot: + plotLine = plotNR1.append('plotline', xcol=1, ycol=3) + plotLine.append('colour', 'blue') + plotLine.append('symbolsize', '0') # Completeness - only for servalcat_xtal_norefmac if len(xmlnode.findall('.//cycle[last()]/data/binned/Cmpl')) > 0: From ce8cb35624c0aa426478308fd08093e379d20d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Tue, 2 Sep 2025 09:12:53 +0100 Subject: [PATCH 15/21] fix plot for n_obs when free R was not used --- wrappers/servalcat/script/servalcat_report.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/wrappers/servalcat/script/servalcat_report.py b/wrappers/servalcat/script/servalcat_report.py index 514dc580f..e6d83e983 100644 --- a/wrappers/servalcat/script/servalcat_report.py +++ b/wrappers/servalcat/script/servalcat_report.py @@ -458,8 +458,7 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): plotLine.append('symbolsize', '0') # Number of reflections - only for servalcat_xtal_norefmac - if len(xmlnode.findall('.//cycle[last()]/data/binned/n_obs')) > 0 and \ - len(xmlnode.findall('.//cycle[last()]/data/binned/n_work')) > 0: + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_obs')) > 0: graphNtitle = "Number of reflections" graphN = gallery.addFlotGraph( xmlnode=xmlnode, @@ -470,9 +469,13 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): style=galleryGraphStyle) graphN.addData(title="Resolution(Å)", select=".//cycle[last()]/data/binned/./d_min_4ssqll") graphN.addData(title="Nobs", select=".//cycle[last()]/data/binned/./n_obs") - graphN.addData(title="Nwork", select=".//cycle[last()]/data/binned/./n_work") - if len(xmlnode.findall('.//cycle[last()]/data/binned/n_free')) > 0: - graphN.addData(title="Nfree", select=".//cycle[last()]/data/binned/./n_free") + graphNplots = 1 + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_work')) > 0: + graphN.addData(title="Nwork", select=".//cycle[last()]/data/binned/./n_work") + graphNplots += 1 + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_free')) > 0: + graphN.addData(title="Nfree", select=".//cycle[last()]/data/binned/./n_free") + graphNplots += 1 plotN = graphN.addPlotObject() plotN.append('title', graphNtitle) plotN.append('plottype', 'xy') @@ -482,13 +485,14 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): plotLine = plotN.append('plotline', xcol=1, ycol=2) plotLine.append('colour', 'red') plotLine.append('symbolsize', '0') - plotLine = plotN.append('plotline', xcol=1, ycol=3) - plotLine.append('colour', 'orange') - plotLine.append('symbolsize', '0') - plotN.append('yrange', rightaxis='true') - plotLine = plotN.append('plotline', xcol=1, ycol=4) # , rightaxis='true') - plotLine.append('colour', 'blue') - plotLine.append('symbolsize', '0') + if graphNplots >= 2: + plotLine = plotN.append('plotline', xcol=1, ycol=3) + plotLine.append('colour', 'orange') + plotLine.append('symbolsize', '0') + if graphNplots >= 3: + plotLine = plotN.append('plotline', xcol=1, ycol=4) # , rightaxis='true') + plotLine.append('colour', 'blue') + plotLine.append('symbolsize', '0') # Number of reflections used for R1 calculation - only for servalcat_xtal_norefmac if len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1')) > 0 or \ From 2e37b6359d5de0a7f97111da90344c94dc60903a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Wed, 3 Sep 2025 10:57:43 +0100 Subject: [PATCH 16/21] plots in graph for number of reflections --- wrappers/servalcat/script/servalcat_report.py | 142 ++++++++++++------ 1 file changed, 99 insertions(+), 43 deletions(-) diff --git a/wrappers/servalcat/script/servalcat_report.py b/wrappers/servalcat/script/servalcat_report.py index e6d83e983..738c937d6 100644 --- a/wrappers/servalcat/script/servalcat_report.py +++ b/wrappers/servalcat/script/servalcat_report.py @@ -469,13 +469,28 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): style=galleryGraphStyle) graphN.addData(title="Resolution(Å)", select=".//cycle[last()]/data/binned/./d_min_4ssqll") graphN.addData(title="Nobs", select=".//cycle[last()]/data/binned/./n_obs") - graphNplots = 1 - if len(xmlnode.findall('.//cycle[last()]/data/binned/n_work')) > 0: - graphN.addData(title="Nwork", select=".//cycle[last()]/data/binned/./n_work") - graphNplots += 1 - if len(xmlnode.findall('.//cycle[last()]/data/binned/n_free')) > 0: - graphN.addData(title="Nfree", select=".//cycle[last()]/data/binned/./n_free") - graphNplots += 1 + avail_n_work = False + avail_n_free = False + avail_n_R1work = False + avail_n_R1free = False + avail_n_R1 = False + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1')) > 0: + graphN.addData(title="N_R1", select=".//cycle[last()]/data/binned/./n_R1") + avail_n_R1 = True + else: + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_work')) > 0: + graphN.addData(title="Nwork", select=".//cycle[last()]/data/binned/./n_work") + avail_n_work = True + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_free')) > 0: + graphN.addData(title="Nfree", select=".//cycle[last()]/data/binned/./n_free") + avail_n_free = True + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1work')) > 0: + graphN.addData(title="N_R1work", select=".//cycle[last()]/data/binned/./n_R1work") + avail_n_R1work = True + if len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1free')) > 0: + graphN.addData(title="N_R1free", select=".//cycle[last()]/data/binned/./n_R1free") + avail_n_R1free = True + plotN = graphN.addPlotObject() plotN.append('title', graphNtitle) plotN.append('plottype', 'xy') @@ -483,51 +498,92 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): plotN.append('legendposition', x=0, y=1) plotN.append('xscale', 'oneoversqrt') plotLine = plotN.append('plotline', xcol=1, ycol=2) - plotLine.append('colour', 'red') + plotLine.append('colour', 'gray') plotLine.append('symbolsize', '0') - if graphNplots >= 2: + if avail_n_R1: plotLine = plotN.append('plotline', xcol=1, ycol=3) plotLine.append('colour', 'orange') plotLine.append('symbolsize', '0') - if graphNplots >= 3: - plotLine = plotN.append('plotline', xcol=1, ycol=4) # , rightaxis='true') + else: + if avail_n_work: + plotLine = plotN.append('plotline', xcol=1, ycol=3) + plotLine.append('colour', 'orange') + plotLine.append('symbolsize', '0') + if avail_n_free: + plotLine = plotN.append('plotline', xcol=1, ycol=4) # , rightaxis='true') + plotLine.append('colour', 'blue') + plotLine.append('symbolsize', '0') + if avail_n_R1work: + plotLine = plotN.append('plotline', xcol=1, ycol=5) + plotLine.append('colour', 'red') + plotLine.append('symbolsize', '0') + if avail_n_R1free: + plotLine = plotN.append('plotline', xcol=1, ycol=6) + plotLine.append('colour', 'cyan') + plotLine.append('symbolsize', '0') + + if avail_n_work and avail_n_free and avail_n_R1work and avail_n_R1free: + # plot of only n_obs n_work n_free + plotN2 = graphN.addPlotObject() + plotN2.append('title', "Number of reflections (only Nobs and Nwork and Nfree)") + plotN2.append('plottype', 'xy') + plotN2.append('xlabel', 'Resolution (Å)') + plotN2.append('legendposition', x=0, y=1) + plotN2.append('xscale', 'oneoversqrt') + plotLine = plotN2.append('plotline', xcol=1, ycol=2) + plotLine.append('colour', 'gray') + plotLine.append('symbolsize', '0') + plotLine = plotN2.append('plotline', xcol=1, ycol=3) + plotLine.append('colour', 'orange') + plotLine.append('symbolsize', '0') + plotLine = plotN2.append('plotline', xcol=1, ycol=4) # , rightaxis='true') plotLine.append('colour', 'blue') plotLine.append('symbolsize', '0') - # Number of reflections used for R1 calculation - only for servalcat_xtal_norefmac - if len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1')) > 0 or \ - len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1work')) > 0 or \ - len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1free')) > 0: - graphNR1title = "Number of reflections used for R1 calculation" - graphNR1 = gallery.addFlotGraph( - xmlnode=xmlnode, - title=graphNR1title, - internalId=graphNR1title, - outputXml=self.outputXml, - label=graphNR1title, - style=galleryGraphStyle) - second_plot = False - graphNR1.addData(title="Resolution(Å)", select=".//cycle[last()]/data/binned/./d_min_4ssqll") - if len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1')) > 0: - graphNR1.addData(title="N_R1", select=".//cycle[last()]/data/binned/./n_R1") - elif len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1work')) > 0: - graphNR1.addData(title="N_R1work", select=".//cycle[last()]/data/binned/./n_R1work") - if len(xmlnode.findall('.//cycle[last()]/data/binned/n_R1free')) > 0: - graphNR1.addData(title="N_R1free", select=".//cycle[last()]/data/binned/./n_R1free") - second_plot = True - plotNR1 = graphNR1.addPlotObject() - plotNR1.append('title', graphNR1title) - plotNR1.append('plottype', 'xy') - plotNR1.append('xlabel', 'Resolution (Å)') - plotNR1.append('legendposition', x=0, y=1) - plotNR1.append('xscale', 'oneoversqrt') - plotLine = plotNR1.append('plotline', xcol=1, ycol=2) - plotLine.append('colour', 'orange') - plotLine.append('symbolsize', '0') - if second_plot: - plotLine = plotNR1.append('plotline', xcol=1, ycol=3) + if avail_n_R1work and avail_n_R1free: + plotN3 = graphN.addPlotObject() + plotN3.append('title', "Number of reflections (only N_R1work and N_R1free)") + plotN3.append('plottype', 'xy') + plotN3.append('xlabel', 'Resolution (Å)') + plotN3.append('legendposition', x=0, y=1) + plotN3.append('xscale', 'oneoversqrt') + plotLine = plotN3.append('plotline', xcol=1, ycol=5) + plotLine.append('colour', 'red') + plotLine.append('symbolsize', '0') + plotLine = plotN3.append('plotline', xcol=1, ycol=6) + plotLine.append('colour', 'cyan') + plotLine.append('symbolsize', '0') + + if avail_n_work and avail_n_R1work: + plotN4 = graphN.addPlotObject() + plotN4.append('title', "Number of reflections (only Nwork and N_R1work)") + plotN4.append('plottype', 'xy') + plotN4.append('xlabel', 'Resolution (Å)') + plotN4.append('legendposition', x=0, y=1) + plotN4.append('xscale', 'oneoversqrt') + plotLine = plotN4.append('plotline', xcol=1, ycol=2) + plotLine.append('colour', 'gray') + plotLine.append('symbolsize', '0') + plotLine = plotN4.append('plotline', xcol=1, ycol=3) + plotLine.append('colour', 'orange') + plotLine.append('symbolsize', '0') + plotLine = plotN4.append('plotline', xcol=1, ycol=5) + plotLine.append('colour', 'red') + plotLine.append('symbolsize', '0') + + if avail_n_free and avail_n_R1free: + plotN5 = graphN.addPlotObject() + plotN5.append('title', "Number of reflections (only Nfree and N_R1free)") + plotN5.append('plottype', 'xy') + plotN5.append('xlabel', 'Resolution (Å)') + plotN5.append('legendposition', x=0, y=1) + plotN5.append('xscale', 'oneoversqrt') + plotLine = plotN5.append('plotline', xcol=1, ycol=4) plotLine.append('colour', 'blue') plotLine.append('symbolsize', '0') + plotLine = plotN5.append('plotline', xcol=1, ycol=6) + plotLine.append('colour', 'cyan') + plotLine.append('symbolsize', '0') # Completeness - only for servalcat_xtal_norefmac if len(xmlnode.findall('.//cycle[last()]/data/binned/Cmpl')) > 0: From a2d375654c25451422b996fdff455f666f722e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Wed, 3 Sep 2025 11:20:39 +0100 Subject: [PATCH 17/21] graph for for MnFo and MnFc --- wrappers/servalcat/script/servalcat_report.py | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/wrappers/servalcat/script/servalcat_report.py b/wrappers/servalcat/script/servalcat_report.py index 738c937d6..7bde0c24b 100644 --- a/wrappers/servalcat/script/servalcat_report.py +++ b/wrappers/servalcat/script/servalcat_report.py @@ -608,22 +608,33 @@ def addGraphsVsResolution(self, parent=None, xmlnode=None, internalIdPrefix=''): plotLine.append('colour', 'orange') plotLine.append('symbolsize', '0') - # MnIo, MnIc - only for servalcat_xtal_norefmac - if len(xmlnode.findall('.//cycle[last()]/data/binned/MnIo')) > 0 and \ - len(xmlnode.findall('.//cycle[last()]/data/binned/MnIc')) > 0: - graphMnIoIcTitle = "Mean Io and Ic" - graphMnIoIc = gallery.addFlotGraph( + # MnIo & MnIc or MnFo & MnFc - only for servalcat_xtal_norefmac + if ( + len(xmlnode.findall('.//cycle[last()]/data/binned/MnIo')) > 0 + or len(xmlnode.findall('.//cycle[last()]/data/binned/MnFo')) > 0 + ): + if len(xmlnode.findall('.//cycle[last()]/data/binned/MnIo')) > 0 and \ + len(xmlnode.findall('.//cycle[last()]/data/binned/MnIc')) > 0: + graphMnOCTitle = "Mean Io and Ic" + MnO = "MnIo" + MnC = "MnIc" + elif len(xmlnode.findall('.//cycle[last()]/data/binned/MnFo')) > 0 and \ + len(xmlnode.findall('.//cycle[last()]/data/binned/MnFc')) > 0: + graphMnOCTitle = "Mean Fo and Fc" + MnO = "MnFo" + MnC = "MnFc" + graphMnOC = gallery.addFlotGraph( xmlnode=xmlnode, - title=graphMnIoIcTitle, - internalId=graphMnIoIcTitle, + title=graphMnOCTitle, + internalId=graphMnOCTitle, outputXml=self.outputXml, - label=graphMnIoIcTitle, + label=graphMnOCTitle, style=galleryGraphStyle) - graphMnIoIc.addData(title="Resolution(Å)", select=".//cycle[last()]/data/binned/./d_min_4ssqll") - graphMnIoIc.addData(title="MeanIo", select=".//cycle[last()]/data/binned/./MnIo") - graphMnIoIc.addData(title="MeanIc", select=".//cycle[last()]/data/binned/./MnIc") - plotMnIoIc = graphMnIoIc.addPlotObject() - plotMnIoIc.append('title', graphMnIoIcTitle) + graphMnOC.addData(title="Resolution(Å)", select=".//cycle[last()]/data/binned/./d_min_4ssqll") + graphMnOC.addData(title=MnO, select=f".//cycle[last()]/data/binned/./{MnO}") + graphMnOC.addData(title=MnC, select=f".//cycle[last()]/data/binned/./{MnC}") + plotMnIoIc = graphMnOC.addPlotObject() + plotMnIoIc.append('title', graphMnOCTitle) plotMnIoIc.append('plottype', 'xy') plotMnIoIc.append('xlabel', 'Resolution (Å)') plotMnIoIc.append('xscale', 'oneoversqrt') From 19b07593bd9a26198c98ea18a5bc1eac8662a61f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Wed, 3 Sep 2025 21:36:16 +0100 Subject: [PATCH 18/21] option to run tortoize within iris - works for servalcat_pipe --- pipelines/servalcat_pipe/script/servalcat_pipe.def.xml | 6 ++++++ pipelines/servalcat_pipe/script/servalcat_pipe.py | 7 ++++++- pipelines/servalcat_pipe/script/servalcat_pipe_gui.py | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml b/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml index 395f370c2..61de86088 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml +++ b/pipelines/servalcat_pipe/script/servalcat_pipe.def.xml @@ -584,6 +584,12 @@ True + + CBoolean + + True + + CBoolean diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe.py b/pipelines/servalcat_pipe/script/servalcat_pipe.py index bd9832275..93bcf14c9 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe.py +++ b/pipelines/servalcat_pipe/script/servalcat_pipe.py @@ -331,11 +331,15 @@ def multimericValidation(self): if hasattr(self.container.controlParameters,"VALIDATE_RAMACHANDRAN"): validate_ramachandran = self.container.controlParameters.VALIDATE_RAMACHANDRAN + validate_tortoize = False + if hasattr(self.container.controlParameters,"VALIDATE_TORTOIZE"): + validate_tortoize = self.container.controlParameters.VALIDATE_TORTOIZE + validate_molprobity = False if hasattr(self.container.controlParameters,"VALIDATE_MOLPROBITY"): validate_molprobity = self.container.controlParameters.VALIDATE_MOLPROBITY - if validate_iris or validate_baverage or validate_molprobity or validate_ramachandran: + if validate_iris or validate_baverage or validate_molprobity or validate_ramachandran or validate_tortoize: self.validate = self.makePluginObject('validate_protein') self.validate.container.inputData.XYZIN_2.set(self.container.outputData.CIFFILE) self.validate.container.inputData.XYZIN_1.set(self.container.inputData.XYZIN) @@ -355,6 +359,7 @@ def multimericValidation(self): self.validate.container.controlParameters.DO_IRIS.set(validate_iris) self.validate.container.controlParameters.DO_BFACT.set(validate_baverage) self.validate.container.controlParameters.DO_RAMA.set(validate_ramachandran) + self.validate.container.controlParameters.DO_TORTOIZE.set(validate_tortoize) self.validate.container.controlParameters.DO_MOLPROBITY.set(validate_molprobity) self.validate.doAsync = False diff --git a/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py b/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py index 107de48fb..ccfefbe5c 100644 --- a/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py +++ b/pipelines/servalcat_pipe/script/servalcat_pipe_gui.py @@ -459,6 +459,7 @@ def drawAdvanced( self ): self.openSubFrame(frame=[True]) self.createLine( [ 'widget', 'VALIDATE_IRIS', 'label', 'Generate Iris validation report' ] ) self.createLine( [ 'widget', 'VALIDATE_RAMACHANDRAN', 'label', 'Generate Ramachandran plots' ] ) + self.createLine( [ 'widget', 'VALIDATE_TORTOIZE', 'label', 'Calculate Rama-Z score using tortoize' ] ) self.createLine( [ 'widget', 'VALIDATE_MOLPROBITY', 'label', 'Run MolProbity to analyse geometry' ] ) self.createLine( [ 'widget', 'RUN_ADP_ANALYSIS', 'label', 'Run ADP analysis' ] ) From 952896d59ec27b28a6c2d055d97cae482bd88f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Wed, 3 Sep 2025 21:40:46 +0100 Subject: [PATCH 19/21] option to run tortoize within iris - doesn't work for prosmart_refmac yet --- pipelines/prosmart_refmac/script/prosmart_refmac.def.xml | 6 ++++++ pipelines/prosmart_refmac/script/prosmart_refmac.py | 7 ++++++- pipelines/prosmart_refmac/script/prosmart_refmac_gui.py | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pipelines/prosmart_refmac/script/prosmart_refmac.def.xml b/pipelines/prosmart_refmac/script/prosmart_refmac.def.xml index 076cdadc6..076c6f48c 100644 --- a/pipelines/prosmart_refmac/script/prosmart_refmac.def.xml +++ b/pipelines/prosmart_refmac/script/prosmart_refmac.def.xml @@ -474,6 +474,12 @@ True + + CBoolean + + False + + CBoolean diff --git a/pipelines/prosmart_refmac/script/prosmart_refmac.py b/pipelines/prosmart_refmac/script/prosmart_refmac.py index 7b02e2986..bb152ee26 100644 --- a/pipelines/prosmart_refmac/script/prosmart_refmac.py +++ b/pipelines/prosmart_refmac/script/prosmart_refmac.py @@ -639,11 +639,15 @@ def finishUp(self, refmacJob): if hasattr(self.container.controlParameters,"VALIDATE_RAMACHANDRAN"): validate_ramachandran = self.container.controlParameters.VALIDATE_RAMACHANDRAN + validate_tortoize = False + if hasattr(self.container.controlParameters,"VALIDATE_TORTOIZE"): + validate_tortoize = self.container.controlParameters.VALIDATE_TORTOIZE + validate_molprobity = False if hasattr(self.container.controlParameters,"VALIDATE_MOLPROBITY"): validate_molprobity = self.container.controlParameters.VALIDATE_MOLPROBITY - if validate_baverage or validate_molprobity or validate_ramachandran or validate_iris: + if validate_baverage or validate_molprobity or validate_ramachandran or validate_tortoize or validate_iris: xml_validation = etree.SubElement(self.xmlroot,"Validation") xml_validation_status = etree.SubElement(xml_validation,"Success") try: @@ -670,6 +674,7 @@ def finishUp(self, refmacJob): self.validate.container.controlParameters.DO_IRIS = validate_iris self.validate.container.controlParameters.DO_BFACT = validate_baverage self.validate.container.controlParameters.DO_RAMA = validate_ramachandran + self.validate.container.controlParameters.DO_TORTOISE = validate_tortoize self.validate.container.controlParameters.DO_MOLPROBITY = validate_molprobity self.validate.doAsync = False diff --git a/pipelines/prosmart_refmac/script/prosmart_refmac_gui.py b/pipelines/prosmart_refmac/script/prosmart_refmac_gui.py index 1c9e438b2..04245bf77 100644 --- a/pipelines/prosmart_refmac/script/prosmart_refmac_gui.py +++ b/pipelines/prosmart_refmac/script/prosmart_refmac_gui.py @@ -533,9 +533,10 @@ def drawOutput( self ): self.createLine( [ 'subtitle', 'Validation and Analysis' ] ) self.openSubFrame(frame=[True], toggleFunction=[self.ToggleRigidModeOff,['REFINEMENT_MODE']]) - self.createLine( [ 'widget', 'VALIDATE_IRIS', 'label', 'Generate Iris report' ] ) + self.createLine( [ 'widget', 'VALIDATE_IRIS', 'label', 'Generate Iris validation report' ] ) self.createLine( [ 'widget', 'VALIDATE_BAVERAGE', 'label', 'Analyse B-factor distributions' ] ) self.createLine( [ 'widget', 'VALIDATE_RAMACHANDRAN', 'label', 'Generate Ramachandran plots' ] ) + self.createLine( [ 'widget', 'VALIDATE_TORTOIZE', 'label', 'Calculate Rama-Z score using tortoize' ] ) self.createLine( [ 'widget', 'VALIDATE_MOLPROBITY', 'label', 'Run MolProbity to analyse geometry' ] ) #self.createLine( [ 'widget', 'RUN_MOLPROBITY', 'label', 'Run standalone MolProbity (to be deprecated)' ] ) self.closeSubFrame() From 1241117b45f3b032112a996f6282855f97225a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mal=C3=BD?= Date: Thu, 4 Sep 2025 11:18:44 +0100 Subject: [PATCH 20/21] option to run tortoize within iris - works now also for prosmart_refmac --- pipelines/prosmart_refmac/script/prosmart_refmac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipelines/prosmart_refmac/script/prosmart_refmac.py b/pipelines/prosmart_refmac/script/prosmart_refmac.py index bb152ee26..42197e246 100644 --- a/pipelines/prosmart_refmac/script/prosmart_refmac.py +++ b/pipelines/prosmart_refmac/script/prosmart_refmac.py @@ -674,7 +674,7 @@ def finishUp(self, refmacJob): self.validate.container.controlParameters.DO_IRIS = validate_iris self.validate.container.controlParameters.DO_BFACT = validate_baverage self.validate.container.controlParameters.DO_RAMA = validate_ramachandran - self.validate.container.controlParameters.DO_TORTOISE = validate_tortoize + self.validate.container.controlParameters.DO_TORTOIZE = validate_tortoize self.validate.container.controlParameters.DO_MOLPROBITY = validate_molprobity self.validate.doAsync = False From adbf1a3d68f1b831269cb8880f2701fc53957ecc Mon Sep 17 00:00:00 2001 From: Paul Bond Date: Tue, 21 Oct 2025 17:19:36 +0100 Subject: [PATCH 21/21] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c53fbb974..78091e63e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Fix for reflection CIF files containing both merged and unmerged data - New Pointless options to remove lattice centering reflections - Fix to Servalcat report type handling +- Added option to do Tortoize Rama-Z calculation in Refmac & Servalcat pipelines ## [2.4.1] - 2025-10-07