Coverage for biobb_dna/curvesplus/biobb_canal.py: 73%

88 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-07 09:06 +0000

1#!/usr/bin/env python3 

2 

3"""Module containing the Canal class and the command line interface.""" 

4import os 

5import shutil 

6import zipfile 

7import argparse 

8from pathlib import Path 

9 

10from biobb_common.generic.biobb_object import BiobbObject 

11from biobb_common.configuration import settings 

12from biobb_common.tools import file_utils as fu 

13from biobb_common.tools.file_utils import launchlogger 

14 

15 

16class Canal(BiobbObject): 

17 """ 

18 | biobb_dna Canal 

19 | Wrapper for the Canal executable that is part of the Curves+ software suite. 

20 

21 Args: 

22 input_cda_file (str): Input cda file, from Cur+ output. File type: input. `Sample file <https://raw.githubusercontent.com/bioexcel/biobb_dna/master/biobb_dna/test/data/curvesplus/curves_output.cda>`_. Accepted formats: cda (edam:format_2330). 

23 input_lis_file (str) (Optional): Input lis file, from Cur+ output. File type: input. Accepted formats: lis (edam:format_2330). 

24 output_zip_path (str): zip filename for output files. File type: output. `Sample file <https://raw.githubusercontent.com/bioexcel/biobb_dna/master/biobb_dna/test/reference/curvesplus/canal_output.zip>`_. Accepted formats: zip (edam:format_3987). 

25 properties (dic): 

26 * **bases** (*str*) - (None) sequence of bases to be searched for in the I/P data (default is blank, meaning no specified sequence). 

27 * **itst** (*int*) - (0) Iteration start index. 

28 * **itnd** (*int*) - (0) Iteration end index. 

29 * **itdel** (*int*) - (1) Iteration delimiter. 

30 * **lev1** (*int*) - (0) Lower base level limit (i.e. base pairs) used for analysis. 

31 * **lev2** (*int*) - (0) Upper base level limit used for analysis. If lev1 > 0 and lev2 = 0, lev2 is set to lev1 (i.e. analyze lev1 only). If lev1=lev2=0, lev1 is set to 1 and lev2 is set to the length of the oligmer (i.e. analyze all levels). 

32 * **nastr** (*str*) - ('NA') character string used to indicate missing data in .ser files. 

33 * **cormin** (*float*) - (0.6) minimal absolute value for printing linear correlation coefficients between pairs of analyzed variables. 

34 * **series** (*str*) - (False) if True then output spatial or time series data. Only possible for the analysis of single structures or single trajectories. 

35 * **histo** (*str*) - (False) if True then output histogram data. 

36 * **corr** (*str*) - (False) if True than output linear correlation coefficients between all variables. 

37 * **sequence** (*str*) - (Optional) sequence of the first strand of the corresponding DNA fragment, for each .cda file. If not given it will be parsed from .lis file. 

38 * **binary_path** (*str*) - ('Canal') Path to Canal executable, otherwise the program wil look for Canal executable in the binaries folder. 

39 * **remove_tmp** (*bool*) - (True) [WF property] Remove temporal files. 

40 * **restart** (*bool*) - (False) [WF property] Do not execute if output files exist. 

41 Examples: 

42 This is a use example of how to use the building block from Python:: 

43 

44 from biobb_dna.curvesplus.biobb_canal import biobb_canal 

45 prop = { 

46 'series': '.t.', 

47 'histo': '.t.', 

48 'sequence': 'CGCGAATTCGCG' 

49 } 

50 biobb_canal( 

51 input_cda_file='/path/to/curves/output.cda', 

52 output_zip_path='/path/to/output.zip', 

53 properties=prop) 

54 Info: 

55 * wrapped_software: 

56 * name: Canal 

57 * version: >=2.6 

58 * license: BSD 3-Clause 

59 * ontology: 

60 * name: EDAM 

61 * schema: http://edamontology.org/EDAM.owl 

62 """ 

63 

64 def __init__(self, input_cda_file, input_lis_file=None, 

65 output_zip_path=None, properties=None, **kwargs) -> None: 

66 properties = properties or {} 

67 

68 # Call parent class constructor 

69 super().__init__(properties) 

70 self.locals_var_dict = locals().copy() 

71 

72 # Input/Output files 

73 self.io_dict = { 

74 'in': { 

75 'input_cda_file': input_cda_file, 

76 'input_lis_file': input_lis_file, 

77 }, 

78 'out': { 

79 'output_zip_path': output_zip_path 

80 } 

81 } 

82 

83 # Properties specific for BB 

84 self.bases = properties.get('bases', None) 

85 self.nastr = properties.get('nastr', None) 

86 self.cormin = properties.get('cormin', 0.6) 

87 self.lev1 = properties.get('lev1', 0) 

88 self.lev2 = properties.get('lev2', 0) 

89 self.itst = properties.get('itst', 0) 

90 self.itnd = properties.get('itnd', 0) 

91 self.itdel = properties.get('itdel', 1) 

92 self.series = ".t." if properties.get('series', False) else ".f." 

93 self.histo = ".t." if properties.get('histo', False) else ".f." 

94 self.corr = ".t." if properties.get('corr', False) else ".f." 

95 self.sequence = properties.get('sequence', None) 

96 self.binary_path = properties.get('binary_path', 'Canal') 

97 self.properties = properties 

98 

99 # Check the properties 

100 self.check_properties(properties) 

101 self.check_arguments() 

102 

103 @launchlogger 

104 def launch(self) -> int: 

105 """Execute the :class:`Canal <biobb_dna.curvesplus.biobb_canal.Canal>` object.""" 

106 

107 # Setup Biobb 

108 if self.check_restart(): 

109 return 0 

110 self.stage_files() 

111 

112 if self.sequence is None: 

113 if self.io_dict['in']['input_lis_file'] is None: 

114 raise RuntimeError( 

115 "if no sequence is passed in the configuration, " 

116 "you must at least specify `input_lis_file` " 

117 "so sequence can be parsed from there") 

118 lis_lines = Path( 

119 self.io_dict['in']['input_lis_file']).read_text().splitlines() 

120 for line in lis_lines: 

121 if line.strip().startswith("Strand 1"): 

122 self.sequence = line.split(" ")[-1] 

123 fu.log( 

124 f"using sequence {self.sequence} " 

125 f"from {self.io_dict['in']['input_lis_file']}", 

126 self.out_log) 

127 

128 # Creating temporary folder 

129 self.tmp_folder = fu.create_unique_dir(prefix="canal_") 

130 fu.log('Creating %s temporary folder' % self.tmp_folder, self.out_log) 

131 

132 # copy input files to temporary folder 

133 shutil.copy( 

134 self.io_dict['in']['input_cda_file'], 

135 self.tmp_folder) 

136 tmp_cda_path = Path(self.io_dict['in']['input_cda_file']).name 

137 if self.io_dict['in']['input_lis_file'] is not None: 

138 shutil.copy( 

139 self.io_dict['in']['input_lis_file'], 

140 self.tmp_folder) 

141 

142 # change directory to temporary folder 

143 original_directory = os.getcwd() 

144 os.chdir(self.tmp_folder) 

145 

146 # create intructions 

147 instructions = [ 

148 f"{self.binary_path} <<! ", 

149 "&inp", 

150 " lis=canal_output,"] 

151 if self.bases is not None: 

152 # add topology file if needed 

153 fu.log('Appending sequence of bases to be searched to command', 

154 self.out_log, self.global_log) 

155 instructions.append(f" seq={self.bases},") 

156 if self.nastr is not None: 

157 # add topology file if needed 

158 fu.log('Adding null values string specification to command', 

159 self.out_log, self.global_log) 

160 instructions.append(f" nastr={self.nastr},") 

161 

162 instructions = instructions + [ 

163 f" cormin={self.cormin},", 

164 f" lev1={self.lev1},lev2={self.lev2},", 

165 f" itst={self.itst},itnd={self.itnd},itdel={self.itdel},", 

166 f" histo={self.histo},", 

167 f" series={self.series},", 

168 f" corr={self.corr},", 

169 "&end", 

170 f"{tmp_cda_path} {self.sequence}", 

171 "!"] 

172 

173 self.cmd = ["\n".join(instructions)] 

174 fu.log('Creating command line with instructions and required arguments', 

175 self.out_log, self.global_log) 

176 

177 # Run Biobb block 

178 self.run_biobb() 

179 

180 # change back to original directory 

181 os.chdir(original_directory) 

182 

183 # create zipfile and write output inside 

184 zf = zipfile.ZipFile( 

185 Path(self.io_dict["out"]["output_zip_path"]), "w") 

186 for canal_outfile in Path(self.tmp_folder).glob("canal_output*"): 

187 zf.write( 

188 canal_outfile, 

189 arcname=canal_outfile.name) 

190 zf.close() 

191 

192 # Remove temporary file(s) 

193 self.tmp_files.extend([ 

194 self.stage_io_dict.get("unique_dir"), 

195 self.tmp_folder 

196 ]) 

197 self.remove_tmp_files() 

198 

199 self.check_arguments(output_files_created=True, raise_exception=False) 

200 

201 return self.return_code 

202 

203 

204def biobb_canal( 

205 input_cda_file: str, 

206 output_zip_path: str, 

207 input_lis_file: str = None, 

208 properties: dict = None, 

209 **kwargs) -> int: 

210 """Create :class:`Canal <biobb_dna.curvesplus.biobb_canal.Canal>` class and 

211 execute the :meth:`launch() <biobb_dna.curvesplus.biobb_canal.Canal.launch>` method.""" 

212 

213 return Canal( 

214 input_cda_file=input_cda_file, 

215 input_lis_file=input_lis_file, 

216 output_zip_path=output_zip_path, 

217 properties=properties, **kwargs).launch() 

218 

219 

220def main(): 

221 """Command line execution of this building block. Please check the command line documentation.""" 

222 parser = argparse.ArgumentParser(description='Execute Canal from the Curves+ software suite.', 

223 formatter_class=lambda prog: argparse.RawTextHelpFormatter(prog, width=99999)) 

224 parser.add_argument('--config', required=False, help='Configuration file') 

225 

226 required_args = parser.add_argument_group('required arguments') 

227 required_args.add_argument('--input_cda_file', required=True, 

228 help='cda input file from Curves+ output. Accepted formats: cda.') 

229 required_args.add_argument('--output_zip_path', required=True, 

230 help='Filename for .zip file with Canal output. Accepted formats: zip.') 

231 parser.add_argument('--input_lis_file', required=False, 

232 help='lis input file from Curves+ output. Accepted formats: lis.') 

233 

234 args = parser.parse_args() 

235 args.config = args.config or "{}" 

236 properties = settings.ConfReader(config=args.config).get_prop_dic() 

237 

238 biobb_canal( 

239 input_cda_file=args.input_cda_file, 

240 input_lis_file=args.input_lis_file, 

241 output_zip_path=args.output_zip_path, 

242 properties=properties) 

243 

244 

245if __name__ == '__main__': 

246 main()