ORForise 1.6.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. ORForise/Aggregate_Compare.py +378 -0
  2. ORForise/Annotation_Compare.py +317 -0
  3. ORForise/Annotation_Intersector.py +726 -0
  4. ORForise/Aux/StORF_Undetected/Completely_Undetected/Completey_Undetected.py +53 -0
  5. ORForise/Aux/StORF_Undetected/Completely_Undetected/__init__.py +0 -0
  6. ORForise/Aux/StORF_Undetected/StORF_Undetected.py +35 -0
  7. ORForise/Aux/StORF_Undetected/__init__.py +0 -0
  8. ORForise/Aux/StORF_Undetected/unvitiated_Genes/__init__.py +0 -0
  9. ORForise/Aux/StORF_Undetected/unvitiated_Genes/unvitiated_Missed_Genes.py +46 -0
  10. ORForise/Aux/TabToGFF/TabToGFF.py +140 -0
  11. ORForise/Aux/TabToGFF/__init__.py +0 -0
  12. ORForise/Aux/__init__.py +0 -0
  13. ORForise/Comparator.py +882 -0
  14. ORForise/Convert_To_GFF.py +141 -0
  15. ORForise/GFF_Adder.py +543 -0
  16. ORForise/List_Tools.py +56 -0
  17. ORForise/ORForise_Analysis/__init__.py +0 -0
  18. ORForise/ORForise_Analysis/cds_checker.py +77 -0
  19. ORForise/ORForise_Analysis/gene_Lenghts.py +28 -0
  20. ORForise/ORForise_Analysis/genome_Metrics.py +258 -0
  21. ORForise/ORForise_Analysis/hypothetical_gene_predictions.py +88 -0
  22. ORForise/ORForise_Analysis/missed_Gene_Metrics.py +277 -0
  23. ORForise/ORForise_Analysis/parital_Match_Analysis.py +230 -0
  24. ORForise/ORForise_Analysis/result_File_Analysis.py +286 -0
  25. ORForise/ORForise_Analysis/start_Codon_Substitution.py +161 -0
  26. ORForise/StORForise.py +115 -0
  27. ORForise/Tools/Augustus/Augustus.py +54 -0
  28. ORForise/Tools/Augustus/__init__.py +0 -0
  29. ORForise/Tools/Balrog/Balrog.py +56 -0
  30. ORForise/Tools/Balrog/__init__.py +0 -0
  31. ORForise/Tools/EasyGene/EasyGene.py +55 -0
  32. ORForise/Tools/EasyGene/__init__.py +0 -0
  33. ORForise/Tools/FGENESB/FGENESB.py +57 -0
  34. ORForise/Tools/FGENESB/__init__.py +0 -0
  35. ORForise/Tools/FragGeneScan/FragGeneScan.py +54 -0
  36. ORForise/Tools/FragGeneScan/__init__.py +0 -0
  37. ORForise/Tools/GFF/GFF.py +77 -0
  38. ORForise/Tools/GFF/__init__.py +0 -0
  39. ORForise/Tools/GLIMMER3/GLIMMER3.py +59 -0
  40. ORForise/Tools/GLIMMER3/__init__.py +0 -0
  41. ORForise/Tools/GeneMark/GeneMark.py +135 -0
  42. ORForise/Tools/GeneMark/__init__.py +0 -0
  43. ORForise/Tools/GeneMarkHA/GeneMarkHA.py +54 -0
  44. ORForise/Tools/GeneMarkHA/__init__.py +0 -0
  45. ORForise/Tools/GeneMarkHMM/GeneMarkHMM.py +55 -0
  46. ORForise/Tools/GeneMarkHMM/__init__.py +0 -0
  47. ORForise/Tools/GeneMarkS/GeneMarkS.py +54 -0
  48. ORForise/Tools/GeneMarkS/__init__.py +0 -0
  49. ORForise/Tools/GeneMarkS2/GeneMarkS2.py +55 -0
  50. ORForise/Tools/GeneMarkS2/__init__.py +0 -0
  51. ORForise/Tools/MetaGene/MetaGene.py +54 -0
  52. ORForise/Tools/MetaGene/__init__.py +0 -0
  53. ORForise/Tools/MetaGeneAnnotator/MetaGeneAnnotator.py +55 -0
  54. ORForise/Tools/MetaGeneAnnotator/__init__.py +0 -0
  55. ORForise/Tools/MetaGeneMark/MetaGeneMark.py +55 -0
  56. ORForise/Tools/MetaGeneMark/__init__.py +0 -0
  57. ORForise/Tools/Prodigal/Prodigal.py +55 -0
  58. ORForise/Tools/Prodigal/__init__.py +0 -0
  59. ORForise/Tools/Prokka/Prokka.py +57 -0
  60. ORForise/Tools/Prokka/__init__.py +0 -0
  61. ORForise/Tools/StORF-Reporter/StORF-Reporter.py +56 -0
  62. ORForise/Tools/StORF-Reporter/__init__.py +0 -0
  63. ORForise/Tools/TransDecoder/TransDecoder.py +54 -0
  64. ORForise/Tools/TransDecoder/__init__.py +0 -0
  65. ORForise/Tools/__init__.py +0 -0
  66. ORForise/__init__.py +0 -0
  67. ORForise/utils.py +236 -0
  68. orforise-1.6.2.dist-info/METADATA +1038 -0
  69. orforise-1.6.2.dist-info/RECORD +73 -0
  70. orforise-1.6.2.dist-info/WHEEL +5 -0
  71. orforise-1.6.2.dist-info/entry_points.txt +15 -0
  72. orforise-1.6.2.dist-info/licenses/LICENSE +624 -0
  73. orforise-1.6.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,317 @@
1
+ from importlib import import_module
2
+ import argparse
3
+ import sys, os
4
+ import gzip, csv
5
+ import logging
6
+ from datetime import datetime
7
+
8
+
9
+ try:
10
+ from .Comparator import tool_comparison
11
+ from .utils import *
12
+ except (ImportError, ModuleNotFoundError):
13
+ from Comparator import tool_comparison
14
+ from utils import *
15
+
16
+
17
+ ##########################
18
+
19
+ # Consolidate printing and logging into a single block
20
+ def _pct(n, total):
21
+ try:
22
+ return format(100 * n / total, '.2f') + '%'
23
+ except Exception:
24
+ return 'N/A'
25
+
26
+ ##########################
27
+
28
+ def comparator(options):
29
+
30
+
31
+ try:
32
+ try: # Detect whether fasta/gff files are .gz or text and read accordingly
33
+ fasta_in = gzip.open(options.genome_dna, 'rt')
34
+ dna_regions = fasta_load(fasta_in)
35
+ except:
36
+ fasta_in = open(options.genome_dna, 'r', encoding='unicode_escape')
37
+ dna_regions = fasta_load(fasta_in)
38
+ try:
39
+ gff_in = gzip.open(options.reference_annotation, 'rt')
40
+ dna_regions = gff_load(options, gff_in, dna_regions)
41
+ except:
42
+ gff_in = open(options.reference_annotation, 'r', encoding='unicode_escape')
43
+ dna_regions = gff_load(options, gff_in, dna_regions)
44
+ except AttributeError:
45
+ sys.exit("Attribute Error:\nStORF'ed GFF probably already exists - Must be deleted before running (-overwrite)")
46
+ except FileNotFoundError:
47
+ split_path = options.gff.split(os.sep)
48
+ sys.exit("Directory '" + split_path[-2] + "' missing fna/gff files")
49
+ ###############################################
50
+ total_ref_genes = sum(
51
+ len(v[2]) if isinstance(v[2], (list, tuple, set, dict, str)) else 1 for v in dna_regions.values())
52
+
53
+ #############################################
54
+ try:
55
+ tool_ = import_module('Tools.' + options.tool + '.' + options.tool, package='my_current_pkg')
56
+ except ModuleNotFoundError:
57
+ try:
58
+ tool_ = import_module('ORForise.Tools.' + options.tool + '.' + options.tool, package='my_current_pkg')
59
+ except ModuleNotFoundError:
60
+ sys.exit("Tool not available - Did you get the name right?")
61
+ tool_ = getattr(tool_, options.tool)
62
+ all_orfs = tool_(options.tool_prediction, dna_regions)
63
+ results = tool_comparison(all_orfs, dna_regions, options.verbose)
64
+ ############## Printing to std-out and optional csv file
65
+ # Ensure the output directory exists
66
+ os.makedirs(options.outdir, exist_ok=True)
67
+ # Use outname as a directory, basename for files is output-outname
68
+ base_out = os.path.join(options.outdir, f"{os.path.basename(options.outname)}")
69
+
70
+ # Prepare to collect summary stats for all contigs
71
+ contig_summaries = []
72
+
73
+ if options.outdir:
74
+ # Ensure the output directory exists
75
+ os.makedirs(options.outdir, exist_ok=True)
76
+ # Use outname as a directory, basename for files is output-outname
77
+ base_out = os.path.join(options.outdir, f"{os.path.basename(options.outname)}")
78
+ with open(f"{base_out}_summary.txt", 'w', encoding='utf-8') as out_file:
79
+ out_file.write('Genome Used: ' + str(options.genome_dna.split('/')[-1]) + '\n')
80
+ if options.reference_tool:
81
+ out_file.write('Reference Tool Used: ' + str(options.reference_tool) + '\n')
82
+ else:
83
+ out_file.write('Reference Used: ' + str(options.reference_annotation.split('/')[-1]) + '\n')
84
+ out_file.write('Tool Compared: ' + str(options.tool) + '\n')
85
+ out_file.write('Total Number of Reference Genes: ' + str(total_ref_genes) + '\n')
86
+ out_file.write('Number of Contigs: ' + str(len(dna_regions)) + '\n')
87
+ out_file.write(
88
+ 'Contig\tGenes\tORFs\tPerfect_Matches\tPartial_Matches\tMissed_Genes\tUnmatched_ORFs\tMulti_Matched_ORFs\n')
89
+
90
+ for dna_region, result in results.items():
91
+ if result:
92
+ num_current_genes = len(dna_regions[dna_region][2])
93
+ num_orfs = result['pred_metrics']['Number_of_ORFs']
94
+ num_perfect = result['pred_metrics']['Number_of_Perfect_Matches']
95
+ num_partial = len(result['pred_metrics']['partial_Hits'])
96
+ num_missed = len(result['rep_metrics']['genes_Undetected'])
97
+ num_unmatched = len(result['pred_metrics']['unmatched_ORFs'])
98
+ num_multi = len(result['pred_metrics']['multi_Matched_ORFs'])
99
+ # Collect summary for this contig
100
+ contig_summaries.append([dna_region, num_current_genes, num_orfs, num_perfect, num_partial, num_missed, num_unmatched, num_multi])
101
+ num_current_genes = len(dna_regions[dna_region][2])
102
+ genome_name = options.reference_annotation.split('/')[-1].split('.')[0]
103
+ rep_metric_description, rep_metrics = get_rep_metrics(result)
104
+ all_metric_description, all_metrics = get_all_metrics(result)
105
+
106
+ # Safely extract metric values
107
+ num_orfs = result.get('pred_metrics', {}).get('Number_of_ORFs') if isinstance(result, dict) else 'N/A'
108
+ perfect = result.get('pred_metrics', {}).get('Number_of_Perfect_Matches') if isinstance(result, dict) else 0
109
+ partial = len(result.get('pred_metrics', {}).get('partial_Hits', [])) if isinstance(result, dict) else 'N/A'
110
+ missed = len(result.get('rep_metrics', {}).get('genes_Undetected', [])) if isinstance(result, dict) else 'N/A'
111
+ unmatched = len(result.get('pred_metrics', {}).get('unmatched_ORFs', [])) if isinstance(result, dict) else 'N/A'
112
+ multi = len(result.get('pred_metrics', {}).get('multi_Matched_ORFs', [])) if isinstance(result, dict) else 'N/A'
113
+
114
+ lines = [
115
+ f"These are the results for: {dna_region}",
116
+ f"Current Contig: {dna_region}",
117
+ f"Number of Genes: {num_current_genes}",
118
+ f"Number of ORFs: {num_orfs}",
119
+ f"Perfect Matches: {perfect} [{num_current_genes}] - {_pct(perfect, num_current_genes) if isinstance(num_current_genes, (int, float)) else 'N/A'}",
120
+ f"Partial Matches: {partial} [{num_current_genes}] - {_pct(partial, num_current_genes) if isinstance(num_current_genes, (int, float)) else 'N/A'}",
121
+ f"Missed Genes: {missed} [{num_current_genes}] - {_pct(missed, num_current_genes) if isinstance(num_current_genes, (int, float)) else 'N/A'}",
122
+ f"Unmatched ORFs: {unmatched} [{num_current_genes}] - {_pct(unmatched, num_current_genes) if isinstance(num_current_genes, (int, float)) else 'N/A'}",
123
+ f"Multi-matched ORFs: {multi} [{num_current_genes}] - {_pct(multi, num_current_genes) if isinstance(num_current_genes, (int, float)) else 'N/A'}"
124
+ ]
125
+
126
+ full_msg = '\n'.join(lines) + '\n'
127
+ if options.verbose:
128
+ print(full_msg)
129
+ options.output_logger.info(full_msg)
130
+
131
+ # Prepare output directory and file names for each contig
132
+ contig_save = dna_region.replace('/', '_').replace('\\', '_')
133
+ contig_dir = os.path.join(options.outdir, contig_save)
134
+ os.makedirs(contig_dir, exist_ok=True)
135
+ summary_file = os.path.join(contig_dir, "summary.txt")
136
+ csv_file = os.path.join(contig_dir, "metrics.csv")
137
+ perfect_fasta = os.path.join(contig_dir, "perfect_matches.fasta")
138
+ partial_fasta = os.path.join(contig_dir, "partial_matches.fasta")
139
+ missed_fasta = os.path.join(contig_dir, "missed_genes.fasta")
140
+ unmatched_fasta = os.path.join(contig_dir, "unmatched_orfs.fasta")
141
+ multi_fasta = os.path.join(contig_dir, "multi_matched_orfs.fasta")
142
+
143
+ # Write summary to text file
144
+ with open(summary_file, 'w', encoding='utf-8') as sf:
145
+ sf.write('Current Contig: ' + str(dna_region) + '\n')
146
+ sf.write('Number of Genes: ' + str(num_current_genes) + '\n')
147
+ sf.write('Number of ORFs: ' + str(result['pred_metrics']['Number_of_ORFs']) + '\n')
148
+ sf.write('Perfect Matches: ' + str(result['pred_metrics']['Number_of_Perfect_Matches']) + ' [' + str(
149
+ num_current_genes) + '] - ' + format(
150
+ 100 * result['pred_metrics']['Number_of_Perfect_Matches'] / num_current_genes, '.2f') + '%\n')
151
+ sf.write('Partial Matches: ' + str(len(result['pred_metrics']['partial_Hits'])) + ' [' + str(
152
+ num_current_genes) + '] - ' + format(
153
+ 100 * len(result['pred_metrics']['partial_Hits']) / num_current_genes, '.2f') + '%\n')
154
+ sf.write('Missed Genes: ' + str(len(result['rep_metrics']['genes_Undetected'])) + ' [' + str(
155
+ num_current_genes) + '] - ' + format(
156
+ 100 * len(result['rep_metrics']['genes_Undetected']) / num_current_genes, '.2f') + '%\n')
157
+ sf.write('Unmatched ORFs: ' + str(len(result['pred_metrics']['unmatched_ORFs'])) + ' [' + str(
158
+ num_current_genes) + '] - ' + format(
159
+ 100 * len(result['pred_metrics']['unmatched_ORFs']) / num_current_genes, '.2f') + '%\n')
160
+ sf.write('Multi-matched ORFs: ' + str(len(result['pred_metrics']['multi_Matched_ORFs'])) + ' [' + str(
161
+ num_current_genes) + '] - ' + format(
162
+ 100 * len(result['pred_metrics']['multi_Matched_ORFs']) / num_current_genes, '.2f') + '%\n')
163
+
164
+
165
+ # Write metrics to CSV
166
+ tool_out = csv.writer(out_file, quoting=csv.QUOTE_NONE, escapechar=" ")
167
+ tool_out = csv.writer(out_file, quoting=csv.QUOTE_NONE, escapechar=" ") # type: ignore[arg-type]
168
+ tool_out.writerow(['Representative_Metrics:'])
169
+ tool_out.writerow(rep_metric_description.split(','))
170
+ tool_out.writerow([*rep_metrics])
171
+ tool_out.writerow(['Prediction_Metrics:'])
172
+ tool_out.writerow(all_metric_description.split(','))
173
+ tool_out.writerow([*all_metrics])
174
+ tool_out.writerow(['Reference_CDS_Gene_Coverage_of_Genome'])
175
+ tool_out.writerow([''.join(map(str, result['rep_metrics']['gene_Coverage_Genome']))])
176
+ tool_out.writerow(['Predicted_CDS_Coverage_of_Genome'])
177
+ tool_out.writerow([''.join(map(str, result['pred_metrics']['orf_Coverage_Genome']))])
178
+ tool_out.writerow(['Matched_Predicted_CDS_Coverage_of_Genome'])
179
+ tool_out.writerow([''.join(map(str, result['pred_metrics']['matched_ORF_Coverage_Genome']))])
180
+
181
+ # Write perfect matches to FASTA
182
+ with open(perfect_fasta, 'w', encoding='utf-8') as f:
183
+ for key, value in result['pred_metrics'].get('perfect_Matches', {}).items():
184
+ key_parts = key.split(',')
185
+ id = f">{genome_name}_{key_parts[0]}_{key_parts[1]}_{key_parts[2]}_{key_parts[5]}"
186
+ f.write(f"{id}\n{value}\n")
187
+
188
+ # Write partial matches to FASTA
189
+ with open(partial_fasta, 'w', encoding='utf-8') as f:
190
+ for key, value in result['pred_metrics'].get('partial_Hits', {}).items():
191
+ key_parts = key.split(';')
192
+ gene_Seq = value[0]
193
+ orf_Seq = value[1]
194
+ f.write(f">{key_parts[0]}_gene\n{gene_Seq}\n>{key_parts[1]}_orf\n{orf_Seq}\n")
195
+
196
+ # Write missed genes to FASTA
197
+ with open(missed_fasta, 'w', encoding='utf-8') as f:
198
+ for key, value in result['rep_metrics'].get('genes_Undetected', {}).items():
199
+ key_parts = key.split(',')
200
+ id = f">{genome_name}_{key_parts[0]}_{key_parts[1]}_{key_parts[2]}"
201
+ f.write(f"{id}\n{value}\n")
202
+
203
+ # Write unmatched ORFs to FASTA
204
+ with open(unmatched_fasta, 'w', encoding='utf-8') as f:
205
+ for key, value in result['pred_metrics'].get('unmatched_ORFs', {}).items():
206
+ key_parts = key.split(',')
207
+ id = f">{options.tool}_{key_parts[0]}_{key_parts[1]}_{key_parts[2]}"
208
+ f.write(f"{id}\n{value}\n")
209
+
210
+ # Write multi-matched ORFs to FASTA
211
+ with open(multi_fasta, 'w', encoding='utf-8') as f:
212
+ for key, value in result['pred_metrics'].get('multi_Matched_ORFs', {}).items():
213
+ key_parts = key.split(',')
214
+ multi = f">Predicted_CDS:{key_parts[0]}-{key_parts[1]}_Genes:{'|'.join(value)}"
215
+ f.write(f"{multi}\n")
216
+ else:
217
+ if options.verbose:
218
+ print(f"No results to process for dna region - " + str(dna_region))
219
+ options.output_logger.info(f"No results to process for dna region - " + str(dna_region))
220
+
221
+
222
+ # After all contigs, append the summary table to the main summary file
223
+ if options.outdir and contig_summaries:
224
+ with open(f"{base_out}_summary.txt", 'a', encoding='utf-8') as out_file:
225
+ for row in contig_summaries:
226
+ out_file.write('\t'.join(map(str, row)) + '\n')
227
+ # Optionally, add overall totals
228
+ total_genes = sum(row[1] for row in contig_summaries)
229
+ total_orfs = sum(row[2] for row in contig_summaries)
230
+ total_perfect = sum(row[3] for row in contig_summaries)
231
+ total_partial = sum(row[4] for row in contig_summaries)
232
+ total_missed = sum(row[5] for row in contig_summaries)
233
+ total_unmatched = sum(row[6] for row in contig_summaries)
234
+ total_multi = sum(row[7] for row in contig_summaries)
235
+ out_file.write('\nOverall Summary:\n')
236
+ out_file.write(f'Number of Genes: {total_genes}\n')
237
+ out_file.write(f'Number of ORFs: {total_orfs}\n')
238
+ out_file.write(f'Perfect Matches: {total_perfect} [{total_genes}] - {100 * total_perfect / total_genes:.2f}%\n')
239
+ out_file.write(f'Partial Matches: {total_partial} [{total_genes}] - {100 * total_partial / total_genes:.2f}%\n')
240
+ out_file.write(f'Missed Genes: {total_missed} [{total_genes}] - {100 * total_missed / total_genes:.2f}%\n')
241
+ out_file.write(f'Unmatched ORFs: {total_unmatched} [{total_genes}] - {100 * total_unmatched / total_genes:.2f}%\n')
242
+ out_file.write(f'Multi-matched ORFs: {total_multi} [{total_genes}] - {100 * total_multi / total_genes:.2f}%\n')
243
+
244
+ lines = [
245
+ f"Combined metrics for all contigs:",
246
+ f"Number of Genes: {total_genes}",
247
+ f"Number of ORFs: {total_orfs}",
248
+ f"Perfect Matches: {total_perfect} [{total_genes}] - {100 * total_perfect / total_genes:.2f}%",
249
+ f"Partial Matches: {total_partial} [{total_genes}] - {100 * total_partial / total_genes:.2f}%",
250
+ f"Missed Genes: {total_missed} [{total_genes}] - {100 * total_missed / total_genes:.2f}%",
251
+ f"Unmatched ORFs: {total_unmatched} [{total_genes}] - {100 * total_unmatched / total_genes:.2f}%",
252
+ f"Multi-matched ORFs: {total_multi} [{total_genes}] - {100 * total_multi / total_genes:.2f}%"
253
+ ]
254
+
255
+ full_msg = '\n'.join(lines) + '\n'
256
+ if options.verbose:
257
+ print(full_msg)
258
+ options.output_logger.info(full_msg)
259
+
260
+
261
+ def main():
262
+ print(WELCOME)
263
+
264
+ parser = argparse.ArgumentParser(description='ORForise ' + ORForise_Version + ': Annotatione-Compare Run Parameters.')
265
+ parser._action_groups.pop()
266
+
267
+ required = parser.add_argument_group('Required Arguments')
268
+ required.add_argument('-dna', dest='genome_dna', required=True, help='Genome DNA file (.fa) which both annotations '
269
+ 'are based on')
270
+ required.add_argument('-ref', dest='reference_annotation', required=True,
271
+ help='Which reference annotation file to use as reference?')
272
+ required.add_argument('-t', dest='tool', required=True, help='Which tool to analyse?')
273
+ required.add_argument('-tp', dest='tool_prediction', required=True,
274
+ help='Tool genome prediction file (.gff) - Different Tool Parameters'
275
+ ' are compared individually via separate files')
276
+
277
+ optional = parser.add_argument_group('Optional Arguments')
278
+ optional.add_argument('-gene_ident', action='store', dest='gene_ident', default='CDS',
279
+ help='What features to consider as genes? - Default: CDS - '
280
+ 'Provide comma separated list of features to consider as genes (e.g. CDS,exon)')
281
+ optional.add_argument('-rt', dest='reference_tool', required=False,
282
+ help='What type of Annotation to compare to? -- Leave blank for Ensembl reference'
283
+ '- Provide tool name to compare output from two tools')
284
+
285
+ output = parser.add_argument_group('Output')
286
+ output.add_argument('-o', dest='outdir', required=True,
287
+ help='Define directory where detailed output should be places')
288
+ output.add_argument('-n', dest='outname', required=False,
289
+ help='Define output filename(s) prefix - If not provided, filename of reference '
290
+ 'annotation file will be used- <outname>_<contig_id>_ORF_Comparison.csv')
291
+
292
+ misc = parser.add_argument_group('Misc')
293
+ misc.add_argument('-v', dest='verbose', default='False', type=eval, choices=[True, False],
294
+ help='Default - False: Print out runtime status')
295
+ options = parser.parse_args()
296
+
297
+ options.outname = options.outname if options.outname else options.reference_annotation.split('/')[-1].split('.')[0]
298
+
299
+ # Initialise loggers once and store on options
300
+ if not getattr(options, 'logger_initialized', False):
301
+ os.makedirs(options.outdir, exist_ok=True)
302
+ output_log = os.path.join(options.outdir, f"ORForise_{options.outname}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
303
+ logger = logging.getLogger('ORForise.output')
304
+ logger.setLevel(logging.INFO)
305
+ fh_out = logging.FileHandler(output_log, encoding='utf-8')
306
+ fh_out.setFormatter(logging.Formatter('%(message)s'))
307
+ logger.addHandler(fh_out)
308
+
309
+ options.output_logger = logger
310
+ options.logger_initialized = True
311
+
312
+
313
+ comparator(options)
314
+
315
+ if __name__ == "__main__":
316
+ main()
317
+ print("Complete")