ORForise 1.4.3__py3-none-any.whl → 1.5.1__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 (30) hide show
  1. ORForise/Aggregate_Compare.py +318 -133
  2. ORForise/Annotation_Compare.py +294 -125
  3. ORForise/Comparator.py +656 -576
  4. ORForise/ORForise_Analysis/genome_Metrics.py +51 -33
  5. ORForise/Tools/Augustus/Augustus.py +30 -23
  6. ORForise/Tools/Balrog/Balrog.py +31 -23
  7. ORForise/Tools/EasyGene/EasyGene.py +30 -22
  8. ORForise/Tools/FGENESB/FGENESB.py +32 -25
  9. ORForise/Tools/FragGeneScan/FragGeneScan.py +29 -22
  10. ORForise/Tools/GFF/GFF.py +51 -47
  11. ORForise/Tools/GLIMMER_3/GLIMMER_3.py +34 -27
  12. ORForise/Tools/GeneMark/GeneMark.py +46 -40
  13. ORForise/Tools/GeneMark_HA/GeneMark_HA.py +29 -22
  14. ORForise/Tools/GeneMark_HMM/GeneMark_HMM.py +29 -22
  15. ORForise/Tools/GeneMark_S/GeneMark_S.py +29 -22
  16. ORForise/Tools/GeneMark_S_2/GeneMark_S_2.py +29 -25
  17. ORForise/Tools/MetaGene/MetaGene.py +29 -22
  18. ORForise/Tools/MetaGeneAnnotator/MetaGeneAnnotator.py +30 -23
  19. ORForise/Tools/MetaGeneMark/MetaGeneMark.py +30 -23
  20. ORForise/Tools/Prodigal/Prodigal.py +30 -26
  21. ORForise/Tools/Prokka/Prokka.py +30 -25
  22. ORForise/Tools/StORF_Reporter/StORF_Reporter.py +33 -26
  23. ORForise/Tools/TransDecoder/TransDecoder.py +29 -22
  24. ORForise/utils.py +204 -2
  25. {orforise-1.4.3.dist-info → orforise-1.5.1.dist-info}/METADATA +7 -31
  26. {orforise-1.4.3.dist-info → orforise-1.5.1.dist-info}/RECORD +30 -30
  27. {orforise-1.4.3.dist-info → orforise-1.5.1.dist-info}/entry_points.txt +5 -0
  28. {orforise-1.4.3.dist-info → orforise-1.5.1.dist-info}/WHEEL +0 -0
  29. {orforise-1.4.3.dist-info → orforise-1.5.1.dist-info}/licenses/LICENSE +0 -0
  30. {orforise-1.4.3.dist-info → orforise-1.5.1.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,11 @@
1
1
  from importlib import import_module
2
2
  import argparse
3
- import collections
4
- import csv,sys
5
- #####
3
+ import sys, os
4
+ import gzip, csv
5
+ import logging
6
+ from datetime import datetime
7
+
8
+
6
9
  try:
7
10
  from Comparator import tool_comparison
8
11
  except ImportError:
@@ -13,46 +16,43 @@ try:
13
16
  except ImportError:
14
17
  from ORForise.utils import *
15
18
 
19
+
20
+ ##########################
21
+
22
+ # Consolidate printing and logging into a single block
23
+ def _pct(n, total):
24
+ try:
25
+ return format(100 * n / total, '.2f') + '%'
26
+ except Exception:
27
+ return 'N/A'
28
+
16
29
  ##########################
17
30
 
18
31
  def comparator(options):
19
- with open(options.genome_DNA, mode='r') as genome:
20
- genome_Seq = "".join(line.rstrip() for line in genome if not line.startswith('>'))
21
- ##############################################
22
- if not options.reference_tool: # IF using Ensembl for comparison
23
- ref_genes = collections.OrderedDict() # Order is important
24
- count = 0
25
- with open(options.reference_annotation, 'r') as genome_gff:
26
- for line in genome_gff:
27
- line = line.split('\t')
28
- try:
29
- if "CDS" in line[2] and len(line) == 9:
30
- start = int(line[3])
31
- stop = int(line[4])
32
- strand = line[6]
33
- gene_details = [start,stop,strand]
34
- ref_genes.update({count:gene_details})
35
- count += 1
36
- except IndexError:
37
- continue
38
- ref_genes = sortGenes(ref_genes) # sorted GFF refernce
39
- else: # IF using a tool as reference
32
+
33
+
34
+ try:
35
+ try: # Detect whether fasta/gff files are .gz or text and read accordingly
36
+ fasta_in = gzip.open(options.genome_dna, 'rt')
37
+ dna_regions = fasta_load(fasta_in)
38
+ except:
39
+ fasta_in = open(options.genome_dna, 'r', encoding='unicode_escape')
40
+ dna_regions = fasta_load(fasta_in)
40
41
  try:
41
- reference_tool_ = import_module('Tools.' + options.reference_tool + '.' + options.reference_tool,
42
- package='my_current_pkg')
43
- except ModuleNotFoundError:
44
- try:
45
- reference_tool_ = import_module('ORForise.Tools.' + options.reference_tool + '.' + options.reference_tool,
46
- package='my_current_pkg')
47
- except ModuleNotFoundError:
48
- sys.exit("Tool not available")
49
- reference_tool_ = getattr(reference_tool_, options.reference_tool)
50
- ############ Reformatting tool output for ref_genes
51
- ref_genes_tmp = reference_tool_(options.reference_annotation, genome_Seq)
52
- ref_genes = collections.OrderedDict()
53
- for i, (pos, details) in enumerate(ref_genes_tmp.items()):
54
- pos = pos.split(',')
55
- ref_genes.update({i:[pos[0],pos[1],details[0]]})
42
+ gff_in = gzip.open(options.reference_annotation, 'rt')
43
+ dna_regions = gff_load(options, gff_in, dna_regions)
44
+ except:
45
+ gff_in = open(options.reference_annotation, 'r', encoding='unicode_escape')
46
+ dna_regions = gff_load(options, gff_in, dna_regions)
47
+ except AttributeError:
48
+ sys.exit("Attribute Error:\nStORF'ed GFF probably already exists - Must be deleted before running (-overwrite)")
49
+ except FileNotFoundError:
50
+ split_path = options.gff.split(os.sep)
51
+ sys.exit("Directory '" + split_path[-2] + "' missing fna/gff files")
52
+ ###############################################
53
+ total_ref_genes = sum(
54
+ len(v[2]) if isinstance(v[2], (list, tuple, set, dict, str)) else 1 for v in dna_regions.values())
55
+
56
56
  #############################################
57
57
  try:
58
58
  tool_ = import_module('Tools.' + options.tool + '.' + options.tool, package='my_current_pkg')
@@ -62,90 +62,237 @@ def comparator(options):
62
62
  except ModuleNotFoundError:
63
63
  sys.exit("Tool not available - Did you get the name right?")
64
64
  tool_ = getattr(tool_, options.tool)
65
- orfs = tool_(options.tool_prediction, genome_Seq)
66
- all_Metrics, all_rep_Metrics, start_precision, stop_precision, other_starts, other_stops, perfect_Matches, missed_genes, unmatched_orfs, undetected_gene_metrics, unmatched_orf_metrics, orf_Coverage_Genome, matched_ORF_Coverage_Genome, gene_coverage_genome, multi_Matched_ORFs, partial_Hits = tool_comparison(
67
- ref_genes, orfs, genome_Seq, options.verbose)
68
- ############################################# To get default output filename from input file details
69
- genome_name = options.reference_annotation.split('/')[-1].split('.')[0]
70
- metric_description = list(all_Metrics.keys())
71
- metrics = list(all_Metrics.values())
72
- rep_metric_description = list(all_rep_Metrics.keys())
73
- rep_metrics = list(all_rep_Metrics.values())
65
+ all_orfs = tool_(options.tool_prediction, dna_regions)
66
+ results = tool_comparison(all_orfs, dna_regions, options.verbose)
74
67
  ############## Printing to std-out and optional csv file
75
- print('Genome Used: ' + str(options.genome_DNA.split('/')[-1]))
76
- if options.reference_tool:
77
- print('Reference Tool Used: '+str(options.reference_tool))
78
- else:
79
- print('Reference Used: ' + str(options.reference_annotation.split('/')[-1]))
80
- print('Tool Compared: '+str(options.tool))
81
- print('Perfect Matches: ' + str(len(perfect_Matches)) + ' [' + str(len(ref_genes))+ '] - '+ format(100 * len(perfect_Matches)/len(ref_genes),'.2f')+'%')
82
- print('Partial Matches: ' + str(len(partial_Hits)) + ' [' + str(len(ref_genes))+ '] - '+ format(100 * len(partial_Hits)/len(ref_genes),'.2f')+'%')
83
- print('Missed Genes: ' + str(len(missed_genes)) + ' [' + str(len(ref_genes))+ '] - '+ format(100 * len(missed_genes)/len(ref_genes),'.2f')+'%')
84
- if options.outname:
85
- with open(options.outname, 'w', newline='\n', encoding='utf-8') as out_file: # Clear write out of report
86
- tool_out = csv.writer(out_file, quoting=csv.QUOTE_NONE, escapechar=" ")
87
- tool_out.writerow(['Representative_Metrics:'])
88
- tool_out.writerow(rep_metric_description)
89
- tool_out.writerow(rep_metrics)
90
- tool_out.writerow(['All_Metrics:'])
91
- tool_out.writerow(metric_description)
92
- tool_out.writerow(metrics)
93
- tool_out.writerow(['Reference_CDS_Gene_Coverage_of_Genome'])
94
- tool_out.writerow([gene_coverage_genome])
95
- tool_out.writerow(['Predicted_CDS_Coverage_of_Genome'])
96
- tool_out.writerow([orf_Coverage_Genome])
97
- tool_out.writerow(['Matched_Predicted_CDS_Coverage_of_Genome'])
98
- tool_out.writerow([matched_ORF_Coverage_Genome])
99
- tool_out.writerow(['Start_Position_Difference:'])
100
- tool_out.writerow(start_precision)
101
- tool_out.writerow(['Stop_Position_Difference:'])
102
- tool_out.writerow(stop_precision)
103
- tool_out.writerow(['Alternative_Starts_Predicted:'])
104
- tool_out.writerow(other_starts)
105
- tool_out.writerow(['Alternative_Stops_Predicted:'])
106
- tool_out.writerow(other_stops)
107
- tool_out.writerow(['Undetected_Gene_Metrics:'])
108
- tool_out.writerow([
109
- 'ATG_Start,GTG_Start,TTG_Start,ATT_Start,CTG_Start,Alternative_Start_Codon,TGA_Stop,TAA_Stop,TAG_Stop,Alternative_Stop_Codon,Median_Length,ORFs_on_Positive_Strand,ORFs_on_Negative_Strand'])
110
- tool_out.writerow(undetected_gene_metrics)
111
- ####
112
- tool_out.writerow(['Perfect_Match_Genes:'])
113
- for key, value in perfect_Matches.items():
114
- key = key.split(',')
115
- id = ('>' + genome_name + '_' + key[0] + '_' + key[1] + '_' + key[2])
116
- tool_out.writerow([id + '\n' + value + '\n'])
117
- ####
118
- tool_out.writerow(['Partial_Match_Genes:'])
119
- for key, seqs in partial_Hits.items():
120
- key = key.split(';')
121
- gene_Seq = seqs[0]
122
- orf_Seq = seqs[1]
123
- partial = (key[0] + '\n' + gene_Seq + '\n' + key[1] + '\n' + orf_Seq + '\n')
124
- tool_out.writerow([partial])
125
- ####
126
- tool_out.writerow(['\nMissed_Genes:'])
127
- for key, value in missed_genes.items():
128
- key = key.split(',')
129
- id = ('>' + genome_name + '_' + key[0] + '_' + key[1] + '_' + key[2])
130
- tool_out.writerow([id + '\n' + value + '\n'])
131
- tool_out.writerow(['\nPredicted_CDSs_Without_Corresponding_Gene_In_Reference_Metrics:'])
132
- tool_out.writerow([
133
- 'ATG_Start,GTG_Start,TTG_Start,ATT_Start,CTG_Start,Alternative_Start_Codon,TGA_Stop,TAA_Stop,TAG_Stop,Alternative_Stop_Codon,Median_Length,ORFs_on_Positive_Strand,ORFs_on_Negative_Strand'])
134
- tool_out.writerow(unmatched_orf_metrics)
135
- tool_out.writerow(['Predicted_CDS_Without_Corresponding_Gene_in_Reference:'])
136
- for key, value in unmatched_orfs.items():
137
- key = key.split(',')
138
- id = ('>' + options.tool + '_' + key[0] + '_' + key[1] + '_' + key[2])
139
- tool_out.writerow([id + '\n' + value])
140
- tool_out.writerow(['\nPredicted_CDSs_Which_Detected_more_than_one_Gene:'])
141
-
142
- try:
143
- for key, value in multi_Matched_ORFs.items():
144
- key = key.split(',')
145
- multi = ('Predicted_CDS:' + key[0] + '-' + key[1] + '_Genes:' + '|'.join(value))
146
- tool_out.writerow([multi])
147
- except IndexError:
148
- pass
68
+ # Ensure the output directory exists
69
+ os.makedirs(options.outdir, exist_ok=True)
70
+ # Use outname as a directory, basename for files is output-outname
71
+ base_out = os.path.join(options.outdir, f"{os.path.basename(options.outname)}")
72
+
73
+ # Prepare to collect summary stats for all contigs
74
+ contig_summaries = []
75
+
76
+ if options.outdir:
77
+ # Ensure the output directory exists
78
+ os.makedirs(options.outdir, exist_ok=True)
79
+ # Use outname as a directory, basename for files is output-outname
80
+ base_out = os.path.join(options.outdir, f"{os.path.basename(options.outname)}")
81
+ with open(f"{base_out}_summary.txt", 'w', encoding='utf-8') as out_file:
82
+ out_file.write('Genome Used: ' + str(options.genome_dna.split('/')[-1]) + '\n')
83
+ if options.reference_tool:
84
+ out_file.write('Reference Tool Used: ' + str(options.reference_tool) + '\n')
85
+ else:
86
+ out_file.write('Reference Used: ' + str(options.reference_annotation.split('/')[-1]) + '\n')
87
+ out_file.write('Tool Compared: ' + str(options.tool) + '\n')
88
+ out_file.write('Total Number of Reference Genes: ' + str(total_ref_genes) + '\n')
89
+ out_file.write('Number of Contigs: ' + str(len(dna_regions)) + '\n')
90
+ out_file.write(
91
+ 'Contig\tGenes\tORFs\tPerfect_Matches\tPartial_Matches\tMissed_Genes\tUnmatched_ORFs\tMulti_Matched_ORFs\n')
92
+
93
+ for dna_region, result in results.items():
94
+ if result:
95
+ num_current_genes = len(dna_regions[dna_region][2])
96
+ num_orfs = result['pred_metrics']['Number_of_ORFs']
97
+ num_perfect = result['pred_metrics']['Number_of_Perfect_Matches']
98
+ num_partial = len(result['pred_metrics']['partial_Hits'])
99
+ num_missed = len(result['rep_metrics']['genes_Undetected'])
100
+ num_unmatched = len(result['pred_metrics']['unmatched_ORFs'])
101
+ num_multi = len(result['pred_metrics']['multi_Matched_ORFs'])
102
+ # Collect summary for this contig
103
+ contig_summaries.append([dna_region, num_current_genes, num_orfs, num_perfect, num_partial, num_missed, num_unmatched, num_multi])
104
+ num_current_genes = len(dna_regions[dna_region][2])
105
+ genome_name = options.reference_annotation.split('/')[-1].split('.')[0]
106
+ rep_metric_description, rep_metrics = get_rep_metrics(result)
107
+ all_metric_description, all_metrics = get_all_metrics(result)
108
+
109
+ # Safely extract metric values
110
+ num_orfs = result.get('pred_metrics', {}).get('Number_of_ORFs') if isinstance(result, dict) else 'N/A'
111
+ perfect = result.get('pred_metrics', {}).get('Number_of_Perfect_Matches') if isinstance(result, dict) else 0
112
+ partial = len(result.get('pred_metrics', {}).get('partial_Hits', [])) if isinstance(result, dict) else 'N/A'
113
+ missed = len(result.get('rep_metrics', {}).get('genes_Undetected', [])) if isinstance(result, dict) else 'N/A'
114
+ unmatched = len(result.get('pred_metrics', {}).get('unmatched_ORFs', [])) if isinstance(result, dict) else 'N/A'
115
+ multi = len(result.get('pred_metrics', {}).get('multi_Matched_ORFs', [])) if isinstance(result, dict) else 'N/A'
116
+
117
+ lines = [
118
+ f"These are the results for: {dna_region}",
119
+ f"Current Contig: {dna_region}",
120
+ f"Number of Genes: {num_current_genes}",
121
+ f"Number of ORFs: {num_orfs}",
122
+ f"Perfect Matches: {perfect} [{num_current_genes}] - {_pct(perfect, num_current_genes) if isinstance(num_current_genes, (int, float)) else 'N/A'}",
123
+ f"Partial Matches: {partial} [{num_current_genes}] - {_pct(partial, num_current_genes) if isinstance(num_current_genes, (int, float)) else 'N/A'}",
124
+ f"Missed Genes: {missed} [{num_current_genes}] - {_pct(missed, num_current_genes) if isinstance(num_current_genes, (int, float)) else 'N/A'}",
125
+ f"Unmatched ORFs: {unmatched} [{num_current_genes}] - {_pct(unmatched, num_current_genes) if isinstance(num_current_genes, (int, float)) else 'N/A'}",
126
+ f"Multi-matched ORFs: {multi} [{num_current_genes}] - {_pct(multi, num_current_genes) if isinstance(num_current_genes, (int, float)) else 'N/A'}"
127
+ ]
128
+
129
+ full_msg = '\n'.join(lines) + '\n'
130
+ if options.verbose:
131
+ print(full_msg)
132
+ options.output_logger.info(full_msg)
133
+
134
+ # print("These are the results for: " + dna_region + '\n')
135
+ # print('Current Contig: ' + str(dna_region))
136
+ # print('Number of Genes: ' + str(num_current_genes))
137
+ # print('Number of ORFs: ' + str(result['pred_metrics']['Number_of_ORFs']))
138
+ # print('Perfect Matches: ' + str(result['pred_metrics']['Number_of_Perfect_Matches']) + ' [' + str(num_current_genes)+ '] - '+ format(100 * result['pred_metrics']['Number_of_Perfect_Matches']/num_current_genes,'.2f')+'%')
139
+ # print('Partial Matches: ' + str(len(result['pred_metrics']['partial_Hits'])) + ' [' + str(num_current_genes)+ '] - '+ format(100 * len(result['pred_metrics']['partial_Hits'])/num_current_genes,'.2f')+'%')
140
+ # print('Missed Genes: ' + str(len(result['rep_metrics']['genes_Undetected'])) + ' [' + str(num_current_genes)+ '] - '+ format(100 * len(result['rep_metrics']['genes_Undetected'])/num_current_genes,'.2f')+'%')
141
+ # print('Unmatched ORFs: ' + str(len(result['pred_metrics']['unmatched_ORFs'])) + ' [' + str(num_current_genes)+ '] - '+ format(100 * len(result['pred_metrics']['unmatched_ORFs'])/num_current_genes,'.2f')+'%')
142
+ # print('Multi-matched ORFs: ' + str(len(result['pred_metrics']['multi_Matched_ORFs'])) + ' [' + str(num_current_genes)+ '] - '+ format(100 * len(result['pred_metrics']['multi_Matched_ORFs'])/num_current_genes,'.2f')+'%')
143
+
144
+ # Prepare output directory and file names for each contig
145
+ contig_save = dna_region.replace('/', '_').replace('\\', '_')
146
+ contig_dir = os.path.join(options.outdir, contig_save)
147
+ os.makedirs(contig_dir, exist_ok=True)
148
+ summary_file = os.path.join(contig_dir, "summary.txt")
149
+ csv_file = os.path.join(contig_dir, "metrics.csv")
150
+ perfect_fasta = os.path.join(contig_dir, "perfect_matches.fasta")
151
+ partial_fasta = os.path.join(contig_dir, "partial_matches.fasta")
152
+ missed_fasta = os.path.join(contig_dir, "missed_genes.fasta")
153
+ unmatched_fasta = os.path.join(contig_dir, "unmatched_orfs.fasta")
154
+ multi_fasta = os.path.join(contig_dir, "multi_matched_orfs.fasta")
155
+
156
+ # Write summary to text file
157
+ with open(summary_file, 'w', encoding='utf-8') as sf:
158
+ sf.write('Current Contig: ' + str(dna_region) + '\n')
159
+ sf.write('Number of Genes: ' + str(num_current_genes) + '\n')
160
+ sf.write('Number of ORFs: ' + str(result['pred_metrics']['Number_of_ORFs']) + '\n')
161
+ sf.write('Perfect Matches: ' + str(result['pred_metrics']['Number_of_Perfect_Matches']) + ' [' + str(
162
+ num_current_genes) + '] - ' + format(
163
+ 100 * result['pred_metrics']['Number_of_Perfect_Matches'] / num_current_genes, '.2f') + '%\n')
164
+ sf.write('Partial Matches: ' + str(len(result['pred_metrics']['partial_Hits'])) + ' [' + str(
165
+ num_current_genes) + '] - ' + format(
166
+ 100 * len(result['pred_metrics']['partial_Hits']) / num_current_genes, '.2f') + '%\n')
167
+ sf.write('Missed Genes: ' + str(len(result['rep_metrics']['genes_Undetected'])) + ' [' + str(
168
+ num_current_genes) + '] - ' + format(
169
+ 100 * len(result['rep_metrics']['genes_Undetected']) / num_current_genes, '.2f') + '%\n')
170
+ sf.write('Unmatched ORFs: ' + str(len(result['pred_metrics']['unmatched_ORFs'])) + ' [' + str(
171
+ num_current_genes) + '] - ' + format(
172
+ 100 * len(result['pred_metrics']['unmatched_ORFs']) / num_current_genes, '.2f') + '%\n')
173
+ sf.write('Multi-matched ORFs: ' + str(len(result['pred_metrics']['multi_Matched_ORFs'])) + ' [' + str(
174
+ num_current_genes) + '] - ' + format(
175
+ 100 * len(result['pred_metrics']['multi_Matched_ORFs']) / num_current_genes, '.2f') + '%\n')
176
+
177
+
178
+ # Write metrics to CSV
179
+ with open(csv_file, 'w', newline='\n', encoding='utf-8') as out_file:
180
+ tool_out = csv.writer(out_file, quoting=csv.QUOTE_NONE, escapechar=" ")
181
+ tool_out.writerow(['Representative_Metrics:'])
182
+ tool_out.writerow(rep_metric_description.split(','))
183
+ tool_out.writerow([*rep_metrics])
184
+ tool_out.writerow(['Prediction_Metrics:'])
185
+ tool_out.writerow(all_metric_description.split(','))
186
+ tool_out.writerow([*all_metrics])
187
+ tool_out.writerow(['Reference_CDS_Gene_Coverage_of_Genome'])
188
+ tool_out.writerow([''.join(map(str, result['rep_metrics']['gene_Coverage_Genome']))])
189
+ tool_out.writerow(['Predicted_CDS_Coverage_of_Genome'])
190
+ tool_out.writerow([''.join(map(str, result['pred_metrics']['orf_Coverage_Genome']))])
191
+ tool_out.writerow(['Matched_Predicted_CDS_Coverage_of_Genome'])
192
+ tool_out.writerow([''.join(map(str, result['pred_metrics']['matched_ORF_Coverage_Genome']))])
193
+ # tool_out.writerow(['Start_Position_Difference:'])
194
+ # tool_out.writerow(result.get('start_Difference', []))
195
+ # tool_out.writerow(['Stop_Position_Difference:'])
196
+ # tool_out.writerow(result.get('stop_Difference', []))
197
+ # tool_out.writerow(['Alternative_Starts_Predicted:'])
198
+ # tool_out.writerow(result.get('other_Starts', []))
199
+ # tool_out.writerow(['Alternative_Stops_Predicted:'])
200
+ # tool_out.writerow(result.get('other_Stops', []))
201
+ # tool_out.writerow(['Undetected_Gene_Metrics:'])
202
+ # tool_out.writerow([
203
+ # 'ATG_Start,GTG_Start,TTG_Start,ATT_Start,CTG_Start,Alternative_Start_Codon,TGA_Stop,TAA_Stop,TAG_Stop,Alternative_Stop_Codon,Median_Length,ORFs_on_Positive_Strand,ORFs_on_Negative_Strand'
204
+ # ])
205
+ # tool_out.writerow(result.get('undetected_Gene_Metrics', []))
206
+ # tool_out.writerow(['\nPredicted_CDSs_Without_Corresponding_Gene_In_Reference_Metrics:'])
207
+ # tool_out.writerow([
208
+ # 'ATG_Start,GTG_Start,TTG_Start,ATT_Start,CTG_Start,Alternative_Start_Codon,TGA_Stop,TAA_Stop,TAG_Stop,Alternative_Stop_Codon,Median_Length,ORFs_on_Positive_Strand,ORFs_on_Negative_Strand'
209
+ # ])
210
+ # tool_out.writerow(result.get('unmatched_ORF_Metrics', []))
211
+
212
+ # Write perfect matches to FASTA
213
+ with open(perfect_fasta, 'w', encoding='utf-8') as f:
214
+ for key, value in result['pred_metrics'].get('perfect_Matches', {}).items():
215
+ key_parts = key.split(',')
216
+ id = f">{genome_name}_{key_parts[0]}_{key_parts[1]}_{key_parts[2]}_{key_parts[5]}"
217
+ f.write(f"{id}\n{value}\n")
218
+
219
+ # Write partial matches to FASTA
220
+ with open(partial_fasta, 'w', encoding='utf-8') as f:
221
+ for key, value in result['pred_metrics'].get('partial_Hits', {}).items():
222
+ key_parts = key.split(';')
223
+ gene_Seq = value[0]
224
+ orf_Seq = value[1]
225
+ f.write(f">{key_parts[0]}_gene\n{gene_Seq}\n>{key_parts[1]}_orf\n{orf_Seq}\n")
226
+
227
+ # Write missed genes to FASTA
228
+ with open(missed_fasta, 'w', encoding='utf-8') as f:
229
+ for key, value in result['rep_metrics'].get('genes_Undetected', {}).items():
230
+ key_parts = key.split(',')
231
+ id = f">{genome_name}_{key_parts[0]}_{key_parts[1]}_{key_parts[2]}"
232
+ f.write(f"{id}\n{value}\n")
233
+
234
+ # Write unmatched ORFs to FASTA
235
+ with open(unmatched_fasta, 'w', encoding='utf-8') as f:
236
+ for key, value in result['pred_metrics'].get('unmatched_ORFs', {}).items():
237
+ key_parts = key.split(',')
238
+ id = f">{options.tool}_{key_parts[0]}_{key_parts[1]}_{key_parts[2]}"
239
+ f.write(f"{id}\n{value}\n")
240
+
241
+ # Write multi-matched ORFs to FASTA
242
+ with open(multi_fasta, 'w', encoding='utf-8') as f:
243
+ for key, value in result['pred_metrics'].get('multi_Matched_ORFs', {}).items():
244
+ key_parts = key.split(',')
245
+ multi = f">Predicted_CDS:{key_parts[0]}-{key_parts[1]}_Genes:{'|'.join(value)}"
246
+ f.write(f"{multi}\n")
247
+ else:
248
+ if options.verbose:
249
+ print(f"No results to process for dna region - " + str(dna_region))
250
+ options.output_logger.info(f"No results to process for dna region - " + str(dna_region))
251
+
252
+
253
+ # After all contigs, append the summary table to the main summary file
254
+ if options.outdir and contig_summaries:
255
+ with open(f"{base_out}_summary.txt", 'a', encoding='utf-8') as out_file:
256
+ for row in contig_summaries:
257
+ out_file.write('\t'.join(map(str, row)) + '\n')
258
+ # Optionally, add overall totals
259
+ total_genes = sum(row[1] for row in contig_summaries)
260
+ total_orfs = sum(row[2] for row in contig_summaries)
261
+ total_perfect = sum(row[3] for row in contig_summaries)
262
+ total_partial = sum(row[4] for row in contig_summaries)
263
+ total_missed = sum(row[5] for row in contig_summaries)
264
+ total_unmatched = sum(row[6] for row in contig_summaries)
265
+ total_multi = sum(row[7] for row in contig_summaries)
266
+ out_file.write('\nOverall Summary:\n')
267
+ out_file.write(f'Number of Genes: {total_genes}\n')
268
+ out_file.write(f'Number of ORFs: {total_orfs}\n')
269
+ out_file.write(
270
+ f'Perfect Matches: {total_perfect} [{total_genes}] - {format(100 * total_perfect / total_genes, ".2f")}%\n')
271
+ out_file.write(
272
+ f'Partial Matches: {total_partial} [{total_genes}] - {format(100 * total_partial / total_genes, ".2f")}%\n')
273
+ out_file.write(
274
+ f'Missed Genes: {total_missed} [{total_genes}] - {format(100 * total_missed / total_genes, ".2f")}%\n')
275
+ out_file.write(
276
+ f'Unmatched ORFs: {total_unmatched} [{total_genes}] - {format(100 * total_unmatched / total_genes, ".2f")}%\n')
277
+ out_file.write(
278
+ f'Multi-matched ORFs: {total_multi} [{total_genes}] - {format(100 * total_multi / total_genes, ".2f")}%\n')
279
+
280
+ lines = [
281
+ f"Combined metrics for all contigs:",
282
+ f"Number of Genes: {total_genes}",
283
+ f"Number of ORFs: {total_orfs}",
284
+ f"Perfect Matches: {total_perfect} [{total_genes}] - {format(100 * total_perfect / total_genes, ".2f")}%",
285
+ f"Partial Matches: {total_partial} [{total_genes}] - {format(100 * total_partial / total_genes, ".2f")}%",
286
+ f"Missed Genes: {total_missed} [{total_genes}] - {format(100 * total_missed / total_genes, ".2f")}%",
287
+ f"Unmatched ORFs: {total_unmatched} [{total_genes}] - {format(100 * total_unmatched / total_genes, ".2f")}%",
288
+ f"Multi-matched ORFs: {total_multi} [{total_genes}] - {format(100 * total_multi / total_genes, ".2f")}%"
289
+ ]
290
+
291
+ full_msg = '\n'.join(lines) + '\n'
292
+ if options.verbose:
293
+ print(full_msg)
294
+ options.output_logger.info(full_msg)
295
+
149
296
 
150
297
  def main():
151
298
  print("Thank you for using ORForise\nPlease report any issues to: https://github.com/NickJD/ORForise/issues\n#####")
@@ -154,7 +301,7 @@ def main():
154
301
  parser._action_groups.pop()
155
302
 
156
303
  required = parser.add_argument_group('Required Arguments')
157
- required.add_argument('-dna', dest='genome_DNA', required=True, help='Genome DNA file (.fa) which both annotations '
304
+ required.add_argument('-dna', dest='genome_dna', required=True, help='Genome DNA file (.fa) which both annotations '
158
305
  'are based on')
159
306
  required.add_argument('-ref', dest='reference_annotation', required=True,
160
307
  help='Which reference annotation file to use as reference?')
@@ -164,19 +311,41 @@ def main():
164
311
  ' are compared individually via separate files')
165
312
 
166
313
  optional = parser.add_argument_group('Optional Arguments')
314
+ optional.add_argument('-gene_ident', action='store', dest='gene_ident', default='CDS',
315
+ help='What features to consider as genes? - Default: CDS - '
316
+ 'Provide comma separated list of features to consider as genes (e.g. CDS,exon)')
167
317
  optional.add_argument('-rt', dest='reference_tool', required=False,
168
318
  help='What type of Annotation to compare to? -- Leave blank for Ensembl reference'
169
319
  '- Provide tool name to compare output from two tools')
170
320
 
171
321
  output = parser.add_argument_group('Output')
172
- output.add_argument('-o', dest='outname', required=False,
173
- help='Define full output filename (format is CSV) - If not provided, summary will be printed to std-out')
322
+ output.add_argument('-o', dest='outdir', required=True,
323
+ help='Define directory where detailed output should be places')
324
+ output.add_argument('-n', dest='outname', required=False,
325
+ help='Define output filename(s) prefix - If not provided, filename of reference '
326
+ 'annotation file will be used- <outname>_<contig_id>_ORF_Comparison.csv')
174
327
 
175
328
  misc = parser.add_argument_group('Misc')
176
329
  misc.add_argument('-v', dest='verbose', default='False', type=eval, choices=[True, False],
177
330
  help='Default - False: Print out runtime status')
178
331
  options = parser.parse_args()
179
332
 
333
+ options.outname = options.outname if options.outname else options.reference_annotation.split('/')[-1].split('.')[0]
334
+
335
+ # Initialise loggers once and store on options
336
+ if not getattr(options, 'logger_initialized', False):
337
+ os.makedirs(options.outdir, exist_ok=True)
338
+ output_log = os.path.join(options.outdir, f"ORForise_{options.outname}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
339
+ logger = logging.getLogger('ORForise.output')
340
+ logger.setLevel(logging.INFO)
341
+ fh_out = logging.FileHandler(output_log, encoding='utf-8')
342
+ fh_out.setFormatter(logging.Formatter('%(message)s'))
343
+ logger.addHandler(fh_out)
344
+
345
+ options.output_logger = logger
346
+ options.logger_initialized = True
347
+
348
+
180
349
  comparator(options)
181
350
 
182
351
  if __name__ == "__main__":