diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d5a2c7632e717937308cdc6de08d08933ddbb7ef..d555dbf5163fbd73ad4b63f5bddb64d591ff0c61 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,11 +1,14 @@ -Changes in Release 2.2.x +Changes in Release 2.3.0 -------------------------------------------------------------------------------- * Add AAindex databases (Kawashima et al., 2000) * Added experimental molecular structure format OMF (OpenStructure Minimal Format) * Removed GUI components in containers - * DSSP 4.0 support in DSSP binding (https://github.com/PDB-REDO/dssp) + * Support to call external DSSP program has been deprecated. + ost.bindings.dssp.AssignDSSP still exists with the "old" interface but mimics + the equivalent behaviour with the OpenStructure internal secondary structure + and solvent accessibility algorithms. * mol.alg.PDBize does not turn plain polymer chains (not marked peptide or nucleotide) into ligand chains anymore * Remove ENABLE_IMG flag in cmake build system - img module is always built now diff --git a/CMakeLists.txt b/CMakeLists.txt index ba6cfe36f4e204a17cc4bd53e33e9c55cf021599..c118d3d0a100c940124a48699f21918d43dc815a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ cmake_policy(SET CMP0060 NEW) project(OpenStructure CXX C) set (CMAKE_EXPORT_COMPILE_COMMANDS 1) set (OST_VERSION_MAJOR 2) -set (OST_VERSION_MINOR 2) +set (OST_VERSION_MINOR 3) set (OST_VERSION_PATCH 0) set (OST_VERSION_STRING ${OST_VERSION_MAJOR}.${OST_VERSION_MINOR}.${OST_VERSION_PATCH} ) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake_support) diff --git a/docker/Dockerfile b/docker/Dockerfile index ed7b8286f28749ab09d157bf5621664443257119..0711b1a5aad8bf01ebfc501c57bf0f2f77269d6a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,7 +2,7 @@ FROM ubuntu:20.04 # ARGUMENTS ########### -ARG OPENSTRUCTURE_VERSION="2.2.0" +ARG OPENSTRUCTURE_VERSION="2.3.0" ARG SRC_FOLDER="/usr/local/src" ARG CPUS_FOR_MAKE=2 ARG MSMS_VERSION="2.6.1" @@ -32,7 +32,6 @@ RUN apt-get update -y && apt-get install -y cmake \ doxygen \ swig \ clustalw \ - dssp \ locales && \ # CLEANUP rm -rf /var/lib/apt/lists/* diff --git a/modules/base/pymod/table.py b/modules/base/pymod/table.py index 6c9a039f91430512bd800a3c82a51d9d02b047cd..1e20e4eae4100d265a6e7c3c008d9216a4e1516a 100644 --- a/modules/base/pymod/table.py +++ b/modules/base/pymod/table.py @@ -2959,10 +2959,11 @@ Statistics for column %(col)s plt.title(title, size='x-large', fontweight='bold') plt.ylabel(y_title, size='x-large') plt.xlabel(x_title, size='x-large') - - plt.xscale('log', basex=10) + try: + plt.xscale('log', basex=10) + except: + plt.xscale('log', base=10) # breaking change in matplotlib 3.5 plt.xlim(0.001, 1.0) - if save: plt.savefig(save) diff --git a/modules/bindings/doc/dssp.rst b/modules/bindings/doc/dssp.rst index 3a51a870c9c740b495baa5caa53c0d1475818d77..1a087bcfae35cbdab99549f234fd6e2fe4dd5667 100644 --- a/modules/bindings/doc/dssp.rst +++ b/modules/bindings/doc/dssp.rst @@ -13,6 +13,18 @@ hydrogen bonding patterns and geometric features. The program can be downloaded from `<http://swift.cmbi.ru.nl/gv/dssp/>`_. +.. warning:: + Support of the DSSP program has been deprecated. OpenStructure provides + functionality to assign equivalent secondary structures + (:func:`ost.mol.alg.AssignSecStruct`) and solvent accessibility + (:func:`ost.mol.alg.Accessibility`). You're advised to use these + algorithms. + + :func:`AssignDSSP` still exists and provides the "old" interface but + internally uses the OpenStructure impmlementations and does not call + an external program anymore. + + Examples -------------------------------------------------------------------------------- diff --git a/modules/bindings/pymod/blast.py b/modules/bindings/pymod/blast.py index 2cff9231cffc91418d604e2caabb0c57292f647a..ba82c33973b12cb7989ae7130fcd6b48ec4d4a4a 100644 --- a/modules/bindings/pymod/blast.py +++ b/modules/bindings/pymod/blast.py @@ -148,12 +148,12 @@ def CreateDB(infasta, dbout, mkdb_cmd=None): """ if mkdb_cmd==None: try: - exe=settings.Locate('formatdb') - args=[exe, '-i', infasta, '-n', dbout] + exe=settings.Locate('makeblastdb') + args=[exe, '-in', infasta, '-out', dbout, '-dbtype', 'prot'] except: try: - exe=settings.Locate('makeblastdb') - args=[exe, '-in', infasta, '-out', dbout, '-dbtype', 'prot'] + exe=settings.Locate('formatdb') + args=[exe, '-i', infasta, '-n', dbout] except: raise RuntimeError('could not find makeblastdb/formatdb executable') else: @@ -177,10 +177,10 @@ def BlastVersion(blast_location=None): """ try: - blast_exe=settings.Locate('blastall',explicit_file_name=blast_location) + blast_exe=settings.Locate('blastp',explicit_file_name=blast_location) except: try: - blast_exe=settings.Locate('blastp', explicit_file_name=blast_location) + blast_exe=settings.Locate('blastall', explicit_file_name=blast_location) except: raise RuntimeError('could not find blast executable') @@ -242,10 +242,10 @@ def Blast(query, database, gap_open=11, gap_ext=1, matrix='BLOSUM62', if blast_location==None: try: - blast_exe=settings.Locate('blastall') + blast_exe=settings.Locate('blastp') except: try: - blast_exe=settings.Locate('blastp') + blast_exe=settings.Locate('blastall') except: raise RuntimeError('could not find blast executable') else: diff --git a/modules/bindings/pymod/dssp.py b/modules/bindings/pymod/dssp.py index 803ea195004b440212604c5f1d494a53962bdb24..ee179c9a6966283027f5ef93d8c704bc5ba7e8ea 100644 --- a/modules/bindings/pymod/dssp.py +++ b/modules/bindings/pymod/dssp.py @@ -59,12 +59,94 @@ def _CalcRelativeSA(residue_type, absolute_sa): rel=float(absolute_sa)/(solvent_max_list[residue_indices.find(residue_type)]) return rel +# +#def AssignDSSP(ent, pdb_path="", extract_burial_status=False, tmp_dir=None, +# dssp_bin=None): +# """ +# Assign secondary structure states to peptide residues in the structure. This +# function uses the DSSP command line program. +# +# If you already have a DSSP output file and would like to assign the secondary +# structure states to an entity, use :func:`LoadDSSP`. +# +# :param ent: The entity for which the secondary structure should be calculated +# :type ent: :class:`~ost.mol.EntityHandle` or :class:`~ost.mol.EntityView` +# :param extract_burial_status: If true, also extract burial status and store +# as float-property +# ``relative_solvent_accessibility`` at residue +# level +# :param tmp_dir: If set, overrides the default tmp directory of the +# operating system +# :param dssp_bin: The path to the DSSP executable +# :raises: :class:`~ost.settings.FileNotFound` if the dssp executable is not +# in the path. +# :raises: :class:`RuntimeError` when dssp is executed with errors +# """ +# +# if not ent.IsValid(): +# raise ValueError('model entity is not valid') +# if ent.atom_count==0: +# raise ValueError('model entity does not contain any atoms') +# +# dssp_abs_path=settings.Locate(['dsspcmbi','dssp','mkdssp'], env_name='DSSP_EXECUTABLE', +# explicit_file_name=dssp_bin) +# if os.path.isdir(dssp_abs_path): +# raise RuntimeError('"%s" is a directory. Specify path to DSSP binary' % dssp_abs_path) +# if not os.access(dssp_abs_path, os.X_OK): +# raise RuntimeError('"%s" is not executable' % dssp_abs_path) +# +# pdb_path=tempfile.mktemp(suffix=".pdb",prefix="temp_entity", dir=tmp_dir) +# io.SavePDB(ent, pdb_path) +# temp_dssp_path=tempfile.mktemp(suffix=".out",prefix="dssp", dir=tmp_dir) +# +# cmd = [dssp_abs_path, pdb_path, temp_dssp_path] +# s = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) +# if s.returncode == 0: +# try: +# LoadDSSP(temp_dssp_path, ent, extract_burial_status) +# _Cleanup(temp_dssp_path, pdb_path) +# return ent +# except: +# pass # continue with dssp 4 fallback +# +# # either dssp execution failed or output file cannot be loaded +# # both can be the result of using dssp 4 (https://github.com/PDB-REDO/dssp) +# # => try fallback +# +# # dssp 4 desperately wants a CRYST1 record (REMOVE IF NOT NEEDED ANYMORE!) +# # Be aware, Even more stuff is needed if you don't set the CLIBD_MON env var +# # The dssp binding has therefore been deprecated and we mimic to "old" interface +# # using OpenStructure internal functionality +# with open(pdb_path, 'r') as fh: +# file_content = fh.read() +# with open(pdb_path, 'w') as fh: +# fh.write('CRYST1 1.000 1.000 1.000 90.00 90.00 90.00 P 1 1 ' + +# os.linesep + file_content) +# +# # explicitely request dssp output format +# cmd = [dssp_abs_path, '--output-format', 'dssp', pdb_path, temp_dssp_path] +# s_fallback = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) +# +# if s_fallback.returncode == 0: +# try: +# LoadDSSP(temp_dssp_path, ent, extract_burial_status) +# _Cleanup(temp_dssp_path, pdb_path) +# return ent +# except: +# pass # continue with cleanup and raise error +# +# _Cleanup(temp_dssp_path, pdb_path) +# raise RuntimeError('Failed to assign DSSP') + def AssignDSSP(ent, pdb_path="", extract_burial_status=False, tmp_dir=None, dssp_bin=None): """ Assign secondary structure states to peptide residues in the structure. This - function uses the DSSP command line program. + function replaces the "old" AssignDSSP which relies on the DSSP command line + program and uses OpenStructure internal functionality only. The sole purpose + is to retain the "old" interface and you're adviced to directly use + :func:`ost.mol.alg.AssignSecStruct` and :func:`ost.mol.alg.Accessibility`. If you already have a DSSP output file and would like to assign the secondary structure states to an entity, use :func:`LoadDSSP`. @@ -76,11 +158,8 @@ def AssignDSSP(ent, pdb_path="", extract_burial_status=False, tmp_dir=None, ``relative_solvent_accessibility`` at residue level :param tmp_dir: If set, overrides the default tmp directory of the - operating system - :param dssp_bin: The path to the DSSP executable - :raises: :class:`~ost.settings.FileNotFound` if the dssp executable is not - in the path. - :raises: :class:`RuntimeError` when dssp is executed with errors + operating system - deprecated, has no effect + :param dssp_bin: The path to the DSSP executable - deprecated, has no effect """ if not ent.IsValid(): @@ -88,52 +167,24 @@ def AssignDSSP(ent, pdb_path="", extract_burial_status=False, tmp_dir=None, if ent.atom_count==0: raise ValueError('model entity does not contain any atoms') - dssp_abs_path=settings.Locate(['dsspcmbi','dssp','mkdssp'], env_name='DSSP_EXECUTABLE', - explicit_file_name=dssp_bin) - if os.path.isdir(dssp_abs_path): - raise RuntimeError('"%s" is a directory. Specify path to DSSP binary' % dssp_abs_path) - if not os.access(dssp_abs_path, os.X_OK): - raise RuntimeError('"%s" is not executable' % dssp_abs_path) - - pdb_path=tempfile.mktemp(suffix=".pdb",prefix="temp_entity", dir=tmp_dir) - io.SavePDB(ent, pdb_path) - temp_dssp_path=tempfile.mktemp(suffix=".out",prefix="dssp", dir=tmp_dir) - - cmd = [dssp_abs_path, pdb_path, temp_dssp_path] - s = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - if s.returncode == 0: - try: - LoadDSSP(temp_dssp_path, ent, extract_burial_status) - _Cleanup(temp_dssp_path, pdb_path) - return ent - except: - pass # continue with dssp 4 fallback - - # either dssp execution failed or output file cannot be loaded - # both can be the result of using dssp 4 (https://github.com/PDB-REDO/dssp) - # => try fallback - - # dssp 4 desperately wants a CRYST1 record (REMOVE IF NOT NEEDED ANYMORE!) - with open(pdb_path, 'r') as fh: - file_content = fh.read() - with open(pdb_path, 'w') as fh: - fh.write('CRYST1 1.000 1.000 1.000 90.00 90.00 90.00 P 1 1 ' + - os.linesep + file_content) - - # explicitely request dssp output format - cmd = [dssp_abs_path, '--output-format', 'dssp', pdb_path, temp_dssp_path] - s_fallback = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - - if s_fallback.returncode == 0: - try: - LoadDSSP(temp_dssp_path, ent, extract_burial_status) - _Cleanup(temp_dssp_path, pdb_path) - return ent - except: - pass # continue with cleanup and raise error - - _Cleanup(temp_dssp_path, pdb_path) - raise RuntimeError('Failed to assign DSSP') + mol.alg.AssignSecStruct(ent) + if extract_burial_status: + mol.alg.Accessibility(ent, algorithm=mol.alg.DSSP) + # map float properties from Accessibility algorithm to the original + # properties that have been set in the binding + for r in ent.residues: + if r.HasProp("asaAbs"): + asa = round(r.GetFloatProp("asaAbs")) # original DSSP output is rounded + asa_rel = _CalcRelativeSA(r.one_letter_code, asa) # there would be the + # asaRel property but + # it relates to the + # non-rounded asa + r.SetFloatProp("solvent_accessibility", asa) + r.SetFloatProp("relative_solvent_accessibility", asa_rel) + if asa_rel < 0.25: + r.SetStringProp("burial_status", 'b') + else: + r.SetStringProp("burial_status", 'e') def LoadDSSP(file_name, model, extract_burial_status=False, diff --git a/modules/io/src/mol/omf.cc b/modules/io/src/mol/omf.cc index 5d62e7fd1abd2e90da441f8baa54c2aaa0846e1b..3d3c144deb6d9a962c5178a1779c804fa2cb9705 100644 --- a/modules/io/src/mol/omf.cc +++ b/modules/io/src/mol/omf.cc @@ -1118,6 +1118,13 @@ ost::mol::EntityHandle OMF::GetBU(int bu_idx) const{ } void OMF::ToStream(std::ostream& stream) const { + + uint32_t magic_number = 42; + stream.write(reinterpret_cast<char*>(&magic_number), sizeof(uint32_t)); + + uint32_t version = 1; + stream.write(reinterpret_cast<char*>(&version), sizeof(uint32_t)); + Dump(stream, residue_definitions_); Dump(stream, biounit_definitions_); Dump(stream, chain_data_); @@ -1127,12 +1134,31 @@ void OMF::ToStream(std::ostream& stream) const { } void OMF::FromStream(std::istream& stream) { + + uint32_t magic_number; + stream.read(reinterpret_cast<char*>(&magic_number), sizeof(uint32_t)); + if(magic_number != 42) { + throw ost::Error("Cannot read corrupted OMF stream"); + } + + uint32_t version; + stream.read(reinterpret_cast<char*>(&version), sizeof(uint32_t)); + if(version != 1) { + std::stringstream ss; + ss << "OST version only supports OMF version 1. Got "<<version; + throw ost::Error(ss.str()); + } + Load(stream, residue_definitions_); Load(stream, biounit_definitions_); Load(stream, chain_data_); Load(stream, bond_chain_names_); Load(stream, bond_atoms_); Load(stream, bond_orders_); + + if(!stream.good()) { + throw ost::Error("Cannot read corrupted OMF stream"); + } } void OMF::FillChain(ost::mol::ChainHandle& chain, ost::mol::XCSEditor& ed, diff --git a/singularity/Singularity b/singularity/Singularity index c36ec32d9f843842586c5415375a47a6d4118a21..dd26e2a39ff55ef5417c5f215ffc0bdcc5775a69 100644 --- a/singularity/Singularity +++ b/singularity/Singularity @@ -1,5 +1,5 @@ BootStrap: docker -From: registry.scicore.unibas.ch/schwede/openstructure:2.2.0-focal +From: registry.scicore.unibas.ch/schwede/openstructure:2.3.0-focal %post ############################################################################## # POST