Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 860
Implementing gemmi-based mmcif reader (with easy extension to PDB/PDBx and mmJSON)#4712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base:develop
Are you sure you want to change the base?
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
aa2a88f218cf437f78e026682d6e817f3a03cc8c80f1bf325d21c2202a1be1577645e691e69429a0c0869c731df8b40ec7bdcbd73401a4d39c336bddef88e4cabfd374c9d930184491a3de8565ca6ebbb45077ad9a1a59a27c10d6950cfcf8d1a8b5ef29338caca17ef0e49ccb7ada7ce80632c10f312498353fe35fa187e68fcce263e9f1ba47d539ffb6f2fcfc6c00de720e236b286b562115816b23f92ae16488c64a359b7e29f2c23c8776676e71e60f436b7125b058941ef30fa795572c1a8a94366706bbe8cf9da4f13156bdda981cebdf84947043f69770d7bfd7f70d14930563d7fbb99b9286eb8f3c040f38a2db915aab0d6124834d76cab242aa54fc3a7814fa756e3a9a1fd492b4e1880e4a927d7a0ad0f0bee03c3e54d7920532d7cf90df8c3a05c6ea188dab79a82fe52e3f1714db4601632cd103805089e55c3dbbcd201d081f0b5b22d1ccad1ba434a03b56fbd4c2553d61dc5aed9b5453c51f43e0324c205c910f9f79129a903161c5a549dfc10e6aae46c89cf4027522e1250b0ec81b3d7c1c801d85fbdd070e6b2c6c61a7f607157c3651d01a3f2020484d362f91959d78b15441f0e2e097fbc89c0badcca0bf17062c25424a0b2f6abc2b64581d1d8467bb857a8b026179547eed795600b1e053cd6a7ea15240d77476d4d9a44879d6b39bd4655ec4a33f734ec3b592d348751da65434b6687d4f33c9002a04e70dd71bd7850d80ac2e177a179f92070f3b1b98b28cecf4File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- | ||
| # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 | ||
| # | ||
| """ | ||
| MMCIF structure files in MDAnalysis --- :mod:`MDAnalysis.coordinates.MMCIF` | ||
| =========================================================================== | ||
| .. versionadded:: 2.11.0 | ||
| MDAnalysis reads coordinates from MMCIF (macromolecular Crystallographic | ||
| Information File) files, also known as PDBx/mmCIF format, using the | ||
| `gemmi <https://gemmi.readthedocs.io>`_ library as a backend. MMCIF is a | ||
| more modern and flexible alternative to the PDB format, capable of storing | ||
| detailed structural and experimental data about biological macromolecules. | ||
| MMCIF files use a structured, tabular format with key-value pairs to store | ||
| both coordinate and atom information. The format supports multiple | ||
| models/frames, though this implementation currently only reads the first | ||
| model and provides warning messages for multi-model files. | ||
| The reader automatically detects if the structure contains placeholder unit | ||
| cell information (usually the case for cryoEM structures, where cell | ||
| parameters are (1, 1, 1, 90, 90, 90)) and sets dimensions to ``None`` | ||
| in that case. | ||
| Basic usage | ||
| ----------- | ||
| .. code-block:: python | ||
| import MDAnalysis as mda | ||
| u = mda.Universe("structure.cif") | ||
| # or from a compressed file | ||
| u = mda.Universe("structure.cif.gz") | ||
| See Also | ||
| -------- | ||
| * `wwPDB MMCIF Resources <http://mmcif.wwpdb.org>`_ | ||
| * `Gemmi library documentation <https://gemmi.readthedocs.io>`_ | ||
| Classes | ||
| ------- | ||
| .. autoclass:: MMCIFReader | ||
| :members: | ||
| :inherited-members: | ||
| """ | ||
| import logging | ||
| import warnings | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING | ||
| import numpy as np | ||
| from ..lib import util | ||
| from . import base | ||
| if TYPE_CHECKING: | ||
| from gemmi import Model, Structure | ||
orbeckst marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| try: | ||
| import gemmi | ||
BradyAJohnston marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| HAS_GEMMI = True | ||
| except ImportError: | ||
| HAS_GEMMI = False | ||
| logger = logging.getLogger("MDAnalysis.coordinates.MMCIF") | ||
| def _read_gemmi_structure(filename: str | Path) -> "Structure": | ||
| # This function exists because of some lacking methods in the gemmi Python API. | ||
| # Within gemmi in C++, one can call `read_structure` and in-memory, string, and filepath | ||
| # arguments will all be accepted: | ||
| # https://github.com/project-gemmi/gemmi/blob/4416e298f204b7b57bf5b3051d7efd4fe02957cf/include/gemmi/mmread.hpp#L86 | ||
| # However, for MDA to similarly accept common input types like streams (open File-like objs and StringIO objs) | ||
| # as well as pathlib.Path() objects, we have to use the Python API methods available currently (as of 0.7.3) | ||
| # with a string as a common target for all input types. | ||
| # For this, we call gemmi.cif.read_string (https://gemmi.readthedocs.io/en/latest/cif.html#reading) to handle CIF | ||
| # strings and gemmi.read_pdb_string to handle PDB strings (no one method can handle both formats currently Py-side) | ||
| # openany() is called instead of passing file paths (when available) differently from streams; | ||
| # even though reading the file into a string is less efficient, this is easier to maintain. | ||
| # If the gemmi Python API is extended, this function can be simplified/removed and replaced with something like | ||
| # gemmi.read_structure | ||
| with util.openany(filename) as f: | ||
| content_as_str = f.read() | ||
| try: | ||
| # String -> Doc -> Block -> Structure | ||
| # making Structure from first Block in Document as is done internally in gemmi: | ||
| # https://github.com/project-gemmi/gemmi/blob/4416e298f204b7b57bf5b3051d7efd4fe02957cf/include/gemmi/mmcif.hpp#L32 | ||
| return gemmi.make_structure_from_block( | ||
| gemmi.cif.read_string(content_as_str)[0] | ||
| ) | ||
| except ValueError as e: | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit / ignore if needed] Do we need to guard against an IndexError here too? (i.e. if gemmi.cif.read_string(content_as_str)[0] is blockless?) | ||
| try: | ||
| return gemmi.read_pdb_string(content_as_str) | ||
| except (ValueError, RuntimeError): | ||
| # gemmi raises RuntimeError for unparseable PDB content; | ||
| # re-raise the mmCIF error since that is the primary format here | ||
| raise e | ||
| def _get_coordinates(model: "Model") -> np.ndarray: | ||
| """Get coordinates of all atoms in the `gemmi.Model` object. | ||
| Parameters | ||
| ---------- | ||
| model | ||
| input ``gemmi.Model``, e.g. ``gemmi.read_structure('file.cif')[0]`` | ||
| Returns | ||
| ------- | ||
| np.ndarray, shape [n, 3], where ``n`` is the number of atoms in the structure. | ||
| """ | ||
| return np.array( | ||
| [[*at.pos.tolist()] for chain in model for res in chain for at in res] | ||
BradyAJohnston marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ) | ||
| class MMCIFReader(base.SingleFrameReaderBase): | ||
| """Reads from an MMCIF file using :mod:`gemmi` as a backend. | ||
| Notes | ||
| ----- | ||
| If the structure represents an ensemble, only the first structure in the ensemble | ||
| is read here (and a warning is thrown). Also, if the structure has a placeholder "CRYST1" | ||
| record (1, 1, 1, 90, 90, 90), it's set to ``None`` instead. | ||
| .. versionadded:: 2.11.0 | ||
| """ | ||
| format = ["cif", "cif.gz", "mmcif", "mmcif.gz"] | ||
| units = {"time": None, "length": "Angstrom"} | ||
| def __init__(self, filename, **kwargs): | ||
| if not HAS_GEMMI: | ||
| errmsg = "MMCIFReader: To read mmCIF files, please install gemmi" | ||
| raise ImportError(errmsg) | ||
| super(MMCIFReader, self).__init__(filename, **kwargs) | ||
| def _read_first_frame(self): | ||
| structure = self._get_structure() | ||
| cell_dims = np.array( | ||
| [ | ||
| getattr(structure.cell, name) | ||
| for name in ("a", "b", "c", "alpha", "beta", "gamma") | ||
| ] | ||
| ) | ||
| if len(structure) > 1: | ||
| wmsg = ( | ||
| f"File {self.filename} has {len(structure)} models, " | ||
| "but only the first one will be read" | ||
| ) | ||
| warnings.warn(wmsg) | ||
| logger.warning(wmsg) | ||
| model = structure[0] | ||
| coords = _get_coordinates(model) | ||
| self.n_atoms = len(coords) | ||
| self.ts = self._Timestep.from_coordinates(coords, **self._ts_kwargs) | ||
| if np.allclose(cell_dims, np.array([1.0, 1.0, 1.0, 90.0, 90.0, 90.0])): | ||
| wmsg = ( | ||
| "1 A^3 CRYST1 record," | ||
| " this is usually a placeholder." | ||
| " Unit cell dimensions will be set to None." | ||
| ) | ||
| warnings.warn(wmsg) | ||
| logger.warning(wmsg) | ||
| self.ts.dimensions = None | ||
| else: | ||
| self.ts.dimensions = cell_dims | ||
| self.ts.frame = 0 | ||
| def _get_structure(self): | ||
| return _read_gemmi_structure(self.filename) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -267,6 +267,11 @@ class can choose an appropriate reader automatically. | ||
| | DL_Poly [#a]_ | history | r | DL_Poly ascii history file | | ||
| | | | | :mod:`MDAnalysis.coordinates.DLPOLY` | | ||
| +---------------+-----------+-------+------------------------------------------------------+ | ||
| | MMCIF [#a]_ | cif, | r | Single frame of coordinates from macromolecular | | ||
BradyAJohnston marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| | | mmcif | | structures in the PDBx/mmCIF format (requires the | | ||
| | | | | gemmi_ package). | | ||
| | | | | :mod:`MDAnalysis.coordinates.MMCIF` | | ||
| +---------------+-----------+-------+------------------------------------------------------+ | ||
| | MMTF [#a]_ | mmtf | r | Macromolecular Transmission Format | | ||
| | | | | :mod:`MDAnalysis.coordinates.MMTF` | | ||
| +---------------+-----------+-------+------------------------------------------------------+ | ||
| @@ -297,6 +302,7 @@ class can choose an appropriate reader automatically. | ||
| .. _`netcdf4-python`: https://github.com/Unidata/netcdf4-python | ||
| .. _`H5MD`: https://nongnu.org/h5md/index.html | ||
| .. _`chemfiles`: https://chemfiles.org/ | ||
| .. _`gemmi`: https://gemmi.readthedocs.io/ | ||
| .. _`list of chemfiles file formats`: https://chemfiles.org/chemfiles/latest/formats.html | ||
| .. _`additional tng block data`: https://www.mdanalysis.org/pytng/documentation_pages/Blocks.html | ||
| .. _`PyTNG package`: https://github.com/MDAnalysis/pytng | ||
| @@ -807,3 +813,4 @@ class can choose an appropriate reader automatically. | ||
| from . import NAMDBIN | ||
| from . import FHIAIMS | ||
| from . import TNG | ||
| from . import MMCIF | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.