Coverage for biobb_structure_utils/utils/renumber_structure.py: 85%
88 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-28 11:54 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-28 11:54 +0000
1#!/usr/bin/env python3
3"""Module containing the RenumberStructure class and the command line interface."""
5import argparse
6import json
7from pathlib import Path
8from typing import Optional
10from biobb_common.configuration import settings
11from biobb_common.generic.biobb_object import BiobbObject
12from biobb_common.tools import file_utils as fu
13from biobb_common.tools.file_utils import launchlogger
15from biobb_structure_utils.gro_lib.gro import Gro
16from biobb_structure_utils.utils.common import PDB_SERIAL_RECORDS
19class RenumberStructure(BiobbObject):
20 """
21 | biobb_structure_utils RenumberStructure
22 | Class to renumber atomic indexes from a 3D structure.
23 | Renumber atomic indexes from a 3D structure.
25 Args:
26 input_structure_path (str): Input structure file path. File type: input. `Sample file <https://github.com/bioexcel/biobb_structure_utils/raw/master/biobb_structure_utils/test/data/utils/cl3.noH.pdb>`_. Accepted formats: pdb (edam:format_1476), gro (edam:format_2033).
27 output_structure_path (str): Output structure file path. File type: output. `Sample file <https://github.com/bioexcel/biobb_structure_utils/raw/master/biobb_structure_utils/test/reference/utils/renum_cl3_noH.pdb>`_. Accepted formats: pdb (edam:format_1476), gro (edam:format_2033).
28 output_mapping_json_path (str): Output mapping json file path. File type: output. `Sample file <https://github.com/bioexcel/biobb_structure_utils/raw/master/biobb_structure_utils/test/reference/utils/cl3_output_mapping_json_path.json>`_. Accepted formats: json (edam:format_3464).
29 properties (dic - Python dictionary object containing the tool parameters, not input/output files):
30 * **renumber_residues** (*bool*) - (True) Residue code of the ligand to be removed.
31 * **renumber_residues_per_chain** (*bool*) - (True) Restart residue enumeration every time a new chain is detected.
32 * **remove_tmp** (*bool*) - (True) [WF property] Remove temporal files.
33 * **restart** (*bool*) - (False) [WF property] Do not execute if output files exist.
34 * **sandbox_path** (*str*) - ("./") [WF property] Parent path to the sandbox directory.
36 Examples:
37 This is a use example of how to use the building block from Python::
39 from biobb_structure_utils.utils.renumber_structure import renumber_structure
40 prop = {
41 'renumber_residues': True,
42 'renumber_residues_per_chain': True
43 }
44 renumber_structure(input_structure_path='/path/to/myInputStr.pdb',
45 output_structure_path='/path/to/newStructure.pdb',
46 output_mapping_json_path='/path/to/newMapping.json',
47 properties=prop)
49 Info:
50 * wrapped_software:
51 * name: In house
52 * license: Apache-2.0
53 * ontology:
54 * name: EDAM
55 * schema: http://edamontology.org/EDAM.owl
57 """
59 def __init__(
60 self,
61 input_structure_path,
62 output_structure_path,
63 output_mapping_json_path,
64 properties=None,
65 **kwargs,
66 ) -> None:
67 properties = properties or {}
69 # Call parent class constructor
70 super().__init__(properties)
71 self.locals_var_dict = locals().copy()
73 # Input/Output files
74 self.io_dict = {
75 "in": {"input_structure_path": input_structure_path},
76 "out": {
77 "output_structure_path": output_structure_path,
78 "output_mapping_json_path": output_mapping_json_path,
79 },
80 }
82 # Properties specific for BB
83 self.renumber_residues = properties.get("renumber_residues", True)
84 self.renumber_residues_per_chain = properties.get(
85 "renumber_residues_per_chain", True
86 )
88 # Common in all BB
89 self.can_write_console_log = properties.get("can_write_console_log", True)
90 self.global_log = properties.get("global_log", None)
91 self.prefix = properties.get("prefix", None)
92 self.step = properties.get("step", None)
93 self.path = properties.get("path", "")
94 self.remove_tmp = properties.get("remove_tmp", True)
95 self.restart = properties.get("restart", False)
97 # Check the properties
98 self.check_properties(properties)
99 self.check_arguments()
101 @launchlogger
102 def launch(self) -> int:
103 """Execute the :class:`RenumberStructure <utils.renumber_structure.RenumberStructure>` utils.renumber_structure.RenumberStructure object."""
105 # Setup Biobb
106 if self.check_restart():
107 return 0
108 self.stage_files()
110 # Business code
111 extension = Path(
112 self.stage_io_dict["in"]["input_structure_path"]
113 ).suffix.lower()
114 if extension.lower() == ".gro":
115 fu.log("GRO format detected, reenumerating atoms", self.out_log)
116 gro_st = Gro()
117 gro_st.read_gro_file(self.stage_io_dict["in"]["input_structure_path"])
118 residue_mapping, atom_mapping = gro_st.renumber_atoms(
119 renumber_residues=self.renumber_residues,
120 renumber_residues_per_chain=self.renumber_residues_per_chain,
121 )
122 gro_st.write_gro_file(self.stage_io_dict["out"]["output_structure_path"])
124 else:
125 fu.log("PDB format detected, reenumerating atoms", self.out_log)
126 atom_mapping = {}
127 atom_count = 0
128 residue_mapping = {}
129 residue_count = 0
130 with open(
131 self.stage_io_dict["in"]["input_structure_path"], "r"
132 ) as input_pdb, open(
133 self.stage_io_dict["out"]["output_structure_path"], "w"
134 ) as output_pdb:
135 for line in input_pdb:
136 record = line[:6].upper().strip()
137 if (
138 len(line) > 10 and record in PDB_SERIAL_RECORDS
139 ): # Avoid MODEL, ENDMDL records and empty lines
140 # Renumbering atoms
141 pdb_atom_number = line[6:11].strip()
142 if not atom_mapping.get(
143 pdb_atom_number
144 ): # ANISOU records should have the same numeration as ATOM records
145 atom_count += 1
146 atom_mapping[pdb_atom_number] = str(atom_count)
147 line = line[:6] + "{: >5d}".format(atom_count) + line[11:]
148 # Renumbering residues
149 if self.renumber_residues:
150 chain = line[21]
151 pdb_residue_number = line[22:26].strip()
152 if not residue_mapping.get(chain):
153 residue_mapping[chain] = {}
154 if self.renumber_residues_per_chain:
155 residue_count = 0
156 if not residue_mapping[chain].get(pdb_residue_number):
157 residue_count += 1
158 residue_mapping[chain][pdb_residue_number] = str(
159 residue_count
160 )
161 line = (
162 line[:22] + "{: >4d}".format(residue_count) + line[26:]
163 )
164 output_pdb.write(line)
166 with open(
167 self.stage_io_dict["out"]["output_mapping_json_path"], "w"
168 ) as output_json:
169 output_json.write(
170 json.dumps({"residues": residue_mapping, "atoms": atom_mapping})
171 )
173 self.return_code = 0
174 ##########
176 # Copy files to host
177 self.copy_to_host()
179 # Remove temporal files
180 # self.tmp_files.append(self.stage_io_dict.get("unique_dir", ""))
181 self.remove_tmp_files()
183 self.check_arguments(output_files_created=True, raise_exception=False)
185 return self.return_code
188def renumber_structure(
189 input_structure_path: str,
190 output_structure_path: str,
191 output_mapping_json_path: str,
192 properties: Optional[dict] = None,
193 **kwargs,
194) -> int:
195 """Execute the :class:`RenumberStructure <utils.renumber_structure.RenumberStructure>` class and
196 execute the :meth:`launch() <utils.renumber_structure.RenumberStructure.launch>` method."""
198 return RenumberStructure(
199 input_structure_path=input_structure_path,
200 output_structure_path=output_structure_path,
201 output_mapping_json_path=output_mapping_json_path,
202 properties=properties,
203 **kwargs,
204 ).launch()
206 renumber_structure.__doc__ = RenumberStructure.__doc__
209def main():
210 """Command line execution of this building block. Please check the command line documentation."""
211 parser = argparse.ArgumentParser(
212 description="Renumber atoms and residues from a 3D structure.",
213 formatter_class=lambda prog: argparse.RawTextHelpFormatter(prog, width=99999),
214 )
215 parser.add_argument(
216 "-c",
217 "--config",
218 required=False,
219 help="This file can be a YAML file, JSON file or JSON string",
220 )
222 # Specific args of each building block
223 required_args = parser.add_argument_group("required arguments")
224 required_args.add_argument(
225 "-i", "--input_structure_path", required=True, help="Input structure file name"
226 )
227 required_args.add_argument(
228 "-o",
229 "--output_structure_path",
230 required=True,
231 help="Output structure file name",
232 )
233 required_args.add_argument(
234 "-j",
235 "--output_mapping_json_path",
236 required=True,
237 help="Output mapping json file name",
238 )
240 args = parser.parse_args()
241 config = args.config if args.config else None
242 properties = settings.ConfReader(config=config).get_prop_dic()
244 # Specific call of each building block
245 renumber_structure(
246 input_structure_path=args.input_structure_path,
247 output_structure_path=args.output_structure_path,
248 output_mapping_json_path=args.output_mapping_json_path,
249 properties=properties,
250 )
253if __name__ == "__main__":
254 main()