Coverage for biobb_vs/utils/box.py: 22%
73 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-03 13:34 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-03 13:34 +0000
1#!/usr/bin/env python3
3"""Module containing the Box class and the command line interface."""
4from pathlib import PurePath
5from typing import Optional
6import numpy as np
7from biobb_common.generic.biobb_object import BiobbObject
8from biobb_common.tools import file_utils as fu
9from biobb_common.tools.file_utils import launchlogger
11from biobb_vs.utils.common import (
12 check_input_path,
13 check_output_path,
14 get_box_coordinates,
15)
18class Box(BiobbObject):
19 """
20 | biobb_vs Box
21 | This class sets the center and the size of a rectangular parallelepiped box around a set of residues or a pocket.
22 | Sets the center and the size of a rectangular parallelepiped box around a set of residues from a given PDB or a pocket from a given PQR.
24 Args:
25 input_pdb_path (str): PDB file containing a selection of residue numbers or PQR file containing the pocket. File type: input. `Sample file <https://github.com/bioexcel/biobb_vs/raw/master/biobb_vs/test/data/utils/input_box.pqr>`_. Accepted formats: pdb (edam:format_1476), pqr (edam:format_1476).
26 output_pdb_path (str): PDB including the annotation of the box center and size as REMARKs. File type: output. `Sample file <https://github.com/bioexcel/biobb_vs/raw/master/biobb_vs/test/reference/utils/ref_output_box.pdb>`_. Accepted formats: pdb (edam:format_1476).
27 properties (dic - Python dictionary object containing the tool parameters, not input/output files):
28 * **offset** (*float*) - (2.0) [0.1~1000|0.1] Extra distance (Angstroms) between the last residue atom and the box boundary. The box is centred on the mean of the selected coordinates, so a set of points that is asymmetric about its centre may extend slightly beyond the box faces. Choose an offset large enough to absorb that asymmetry, yet small enough not to enlarge the box past what the binding site needs, since a larger box spreads the same sampling effort over more space.
29 * **box_coordinates** (*bool*) - (False) Add box coordinates as 8 ATOM records.
30 * **remove_tmp** (*bool*) - (True) [WF property] Remove temporal files.
31 * **restart** (*bool*) - (False) [WF property] Do not execute if output files exist.
32 * **sandbox_path** (*str*) - ("./") [WF property] Parent path to the sandbox directory.
34 Examples:
35 This is a use example of how to use the building block from Python::
37 from biobb_vs.utils.box import box
38 prop = {
39 'offset': 2,
40 'box_coordinates': True
41 }
42 box(input_pdb_path='/path/to/myPocket.pqr',
43 output_pdb_path='/path/to/newBox.pdb',
44 properties=prop)
46 Info:
47 * wrapped_software:
48 * name: In house
49 * license: Apache-2.0
50 * ontology:
51 * name: EDAM
52 * schema: http://edamontology.org/EDAM.owl
54 """
56 def __init__(
57 self, input_pdb_path, output_pdb_path, properties=None, **kwargs
58 ) -> None:
59 properties = properties or {}
61 # Call parent class constructor
62 super().__init__(properties)
63 self.locals_var_dict = locals().copy()
65 # Input/Output files
66 self.io_dict = {
67 "in": {"input_pdb_path": input_pdb_path},
68 "out": {"output_pdb_path": output_pdb_path},
69 }
71 # Properties specific for BB
72 self.offset = float(properties.get("offset", 2.0))
73 self.box_coordinates = float(properties.get("box_coordinates", False))
74 self.properties = properties
76 # Check the properties
77 self.check_properties(properties)
78 self.check_arguments()
80 def check_data_params(self, out_log, err_log):
81 """Checks all the input/output paths and parameters"""
82 self.io_dict["in"]["input_pdb_path"] = check_input_path(
83 self.io_dict["in"]["input_pdb_path"],
84 "input_pdb_path",
85 self.out_log,
86 self.__class__.__name__,
87 )
88 self.io_dict["out"]["output_pdb_path"] = check_output_path(
89 self.io_dict["out"]["output_pdb_path"],
90 "output_pdb_path",
91 False,
92 self.out_log,
93 self.__class__.__name__,
94 )
96 @launchlogger
97 def launch(self) -> int:
98 """Execute the :class:`Box <utils.box.Box>` utils.box.Box object."""
100 # check input/output paths and parameters
101 self.check_data_params(self.out_log, self.err_log)
103 # Setup Biobb
104 if self.check_restart():
105 return 0
106 self.stage_files()
108 # check if cavity (pdb) or pocket (pqr)
109 input_type = PurePath(self.io_dict["in"]["input_pdb_path"]).suffix.lstrip(".")
110 if input_type == "pdb":
111 fu.log(
112 "Loading residue PDB selection from %s"
113 % (self.io_dict["in"]["input_pdb_path"]),
114 self.out_log,
115 self.global_log,
116 )
117 else:
118 fu.log(
119 "Loading pocket PQR selection from %s"
120 % (self.io_dict["in"]["input_pdb_path"]),
121 self.out_log,
122 self.global_log,
123 )
125 # get input_pdb_path atoms coordinates
126 selection_atoms_num = 0
127 x_coordslist = []
128 y_coordslist = []
129 z_coordslist = []
130 with open(self.io_dict["in"]["input_pdb_path"]) as infile:
131 for line in infile:
132 if line.startswith("HETATM") or line.startswith("ATOM"):
133 x_coordslist.append(float(line[31:38].strip()))
134 y_coordslist.append(float(line[39:46].strip()))
135 z_coordslist.append(float(line[47:54].strip()))
136 selection_atoms_num = selection_atoms_num + 1
138 # Compute binding site box size
140 # compute box center
141 selection_box_center = [
142 np.average(x_coordslist),
143 np.average(y_coordslist),
144 np.average(z_coordslist),
145 ]
146 fu.log(
147 "Binding site center (Angstroms): %10.3f%10.3f%10.3f"
148 % (
149 selection_box_center[0],
150 selection_box_center[1],
151 selection_box_center[2],
152 ),
153 self.out_log,
154 self.global_log,
155 )
157 # compute box size
158 selection_coords_max = np.amax(
159 [x_coordslist, y_coordslist, z_coordslist], axis=1
160 )
161 selection_box_size = selection_coords_max - selection_box_center
162 if self.offset:
163 fu.log(
164 "Adding %.1f Angstroms offset" % (self.offset),
165 self.out_log,
166 self.global_log,
167 )
168 selection_box_size = [c + self.offset for c in selection_box_size]
170 # SIZE is written as the full edge length, which is what Vina reads from
171 # --size_x/y/z. selection_box_size stays a half-extent because that is
172 # what the corner coordinates and the volume are computed from.
173 selection_box_edge = [2 * side for side in selection_box_size]
174 fu.log(
175 "Binding site box edge lengths (Angstroms): %10.3f%10.3f%10.3f"
176 % (selection_box_edge[0], selection_box_edge[1], selection_box_edge[2]),
177 self.out_log,
178 self.global_log,
179 )
181 # compute volume
182 vol = np.prod(selection_box_size) * 2**3
183 fu.log("Volume (cubic Angstroms): %.0f" % (vol), self.out_log, self.global_log)
185 # add box details as PDB remarks
186 remarks = "REMARK BOX CENTER:%10.3f%10.3f%10.3f" % (
187 selection_box_center[0],
188 selection_box_center[1],
189 selection_box_center[2],
190 )
191 remarks += " SIZE:%10.3f%10.3f%10.3f" % (
192 selection_box_edge[0],
193 selection_box_edge[1],
194 selection_box_edge[2],
195 )
197 selection_box_coords_txt = ""
198 # add (optional) box coordinates as 8 ATOM records
199 if self.box_coordinates:
200 fu.log("Adding box coordinates", self.out_log, self.global_log)
201 selection_box_coords_txt = get_box_coordinates(
202 selection_box_center, selection_box_size
203 )
205 with open(self.io_dict["out"]["output_pdb_path"], "w") as f:
206 f.seek(0, 0)
207 f.write(remarks.rstrip("\r\n") + "\n" + selection_box_coords_txt)
209 fu.log(
210 "Saving output PDB file (with box setting annotations): %s"
211 % (self.io_dict["out"]["output_pdb_path"]),
212 self.out_log,
213 self.global_log,
214 )
216 # Copy files to host
217 self.copy_to_host()
218 self.remove_tmp_files()
220 self.check_arguments(output_files_created=True, raise_exception=False)
222 return 0
225def box(
226 input_pdb_path: str,
227 output_pdb_path: str,
228 properties: Optional[dict] = None,
229 **kwargs,
230) -> int:
231 """Create the :class:`Box <utils.box.Box>` class and
232 execute the :meth:`launch() <utils.box.Box.launch>` method."""
233 return Box(**dict(locals())).launch()
236box.__doc__ = Box.__doc__
237main = Box.get_main(box, "Sets the center and the size of a rectangular parallelepiped box around a set of residues from a given PDB or a pocket from a given PQR.")
240if __name__ == "__main__":
241 main()