PyamilySeq 1.0.0__py3-none-any.whl → 1.1.0__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.
@@ -0,0 +1,321 @@
1
+ import argparse
2
+
3
+
4
+ try:
5
+ from .PyamilySeq_Species import cluster as species_cluster
6
+ from .PyamilySeq_Genus import cluster as genus_cluster
7
+ from .constants import *
8
+ from .utils import *
9
+ except (ModuleNotFoundError, ImportError, NameError, TypeError) as error:
10
+ from PyamilySeq_Species import cluster as species_cluster
11
+ from PyamilySeq_Genus import cluster as genus_cluster
12
+ from constants import *
13
+ from utils import *
14
+
15
+
16
+
17
+
18
+ def run_cd_hit(options, input_file, clustering_output, clustering_mode):
19
+ cdhit_command = [
20
+ clustering_mode,
21
+ '-i', input_file,
22
+ '-o', clustering_output,
23
+ '-c', str(options.pident),
24
+ '-s', str(options.len_diff),
25
+ '-T', str(options.threads),
26
+ '-M', str(options.mem),
27
+ '-d', "0",
28
+ '-g', str(options.fast_mode),
29
+ '-sc', "1",
30
+ '-sf', "1"
31
+ ]
32
+ if options.verbose == True:
33
+ subprocess.run(cdhit_command)
34
+ else:
35
+ subprocess.run(cdhit_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
36
+
37
+
38
+ def main():
39
+ parser = argparse.ArgumentParser(description=f"PyamilySeq {PyamilySeq_Version}: A tool for gene clustering and analysis.")
40
+
41
+ # Add subparsers for Full and Partial modes
42
+ subparsers = parser.add_subparsers(dest="run_mode", required=True, help="Choose a mode: 'Full' or 'Partial'.")
43
+
44
+ # Full Mode Subparser
45
+ full_parser = subparsers.add_parser("Full",
46
+ help="Full mode: PyamilySeq to cluster with CD-HIT and process output.")
47
+ #full_parser.add_argument("-clustering_format", choices=['CD-HIT', 'MMseqs', 'BLAST'], required=True,
48
+ # help="Clustering format to use: CD-HIT, MMseqs2, or BLAST.")
49
+ full_parser.add_argument("-output_dir", required=True,
50
+ help="Directory for all output files.")
51
+ full_parser.add_argument("-input_type", choices=['separate', 'combined', 'fasta'], required=True,
52
+ help="Type of input files: 'separate' for matching FASTA and GFF files, 'combined' for GFF+FASTA, or 'fasta' for a prepared FASTA file.")
53
+ full_parser.add_argument("-input_dir", required=False,
54
+ help="Directory containing GFF/FASTA files - Use with -input_type separate/combined.")
55
+ full_parser.add_argument("-input_fasta", required=False,
56
+ help="Input FASTA file - Use with - input_type fasta.")
57
+ full_parser.add_argument("-name_split_gff", required=False,
58
+ help="Substring to split filenames and extract genome names for gff files (e.g., '_combined.gff3') - Use with -input_type separate/combined.")
59
+ full_parser.add_argument("-name_split_fasta", required=False,
60
+ help="Substring to split filenames and extract genome names for fasta files if named differently to paired gff files (e.g., '_dna.fasta') - Use with -input_type separate/combined.")
61
+ full_parser.add_argument("-sequence_type", choices=['AA', 'DNA'], default="AA", required=False,
62
+ help="Clustering mode: 'DNA' or 'AA'.")
63
+ full_parser.add_argument("-gene_ident", default="CDS", required=False,
64
+ help="Gene identifiers to extract sequences (e.g., 'CDS, tRNA').")
65
+ full_parser.add_argument("-c", type=float, dest="pident", default=0.90, required=False,
66
+ help="Sequence identity threshold for clustering (default: 0.90) - CD-HIT parameter '-c'.")
67
+ full_parser.add_argument("-s", type=float, dest="len_diff", default=0.80, required=False,
68
+ help="Length difference threshold for clustering (default: 0.80) - CD-HIT parameter '-s'.")
69
+ full_parser.add_argument("-fast_mode", action="store_true", required=False,
70
+ help="Enable fast mode for CD-HIT (not recommended) - CD-HIT parameter '-g'.")
71
+
72
+
73
+ # Partial Mode Subparser
74
+ partial_parser = subparsers.add_parser("Partial", help="Partial mode: PyamilySeq to process pre-clustered data.")
75
+ partial_parser.add_argument("-clustering_format", choices=['CD-HIT', 'MMseqs', 'BLAST'], required=True,
76
+ help="Clustering format used: CD-HIT, MMseqs2, or BLAST.")
77
+ partial_parser.add_argument("-cluster_file", required=True,
78
+ help="Cluster file containing pre-clustered groups from CD-HIT, MMseqs, BLAST etc.")
79
+ partial_parser.add_argument("-original_fasta", required=True,
80
+ help="FASTA file used in pre-clustering (Provide sequences in DNA form).")
81
+ partial_parser.add_argument("-output_dir", required=True,
82
+ help="Directory for all output files.")
83
+ partial_parser.add_argument("-reclustered", required=False,
84
+ help="Clustering output file from a second round of clustering.")
85
+ partial_parser.add_argument("-seq_tag", default="StORF", dest="sequence_tag", required=False,
86
+ help="Tag for distinguishing reclustered sequences.")
87
+
88
+ # Common Grouping Arguments
89
+ for subparser in [full_parser, partial_parser]:
90
+ subparser.add_argument("-group_mode", choices=['Species', 'Genus'], default="Species", required=False,
91
+ help="Grouping mode: 'Species' or 'Genus'.")
92
+ subparser.add_argument("-species_groups", default="99,95,15", required=False,
93
+ help="Gene groupings for 'Species' mode (default: '99,95,15').")
94
+ subparser.add_argument("-genus_groups", default="1,2,3,4,5,6,7,8,9,10", required=False,
95
+ help="Gene groupings for 'Genus' mode (default: '1-10').")
96
+ subparser.add_argument("-write_groups", default=None, dest="write_groups", required=False,
97
+ help="Output gene groups as a single FASTA file (specify levels: e.g., '-w 99,95'). - triggers '-wig'.")
98
+ subparser.add_argument("-write_individual_groups", action="store_true", dest="write_individual_groups", required=False,
99
+ help="Output individual FASTA files for each group.")
100
+ subparser.add_argument("-align", action="store_true", dest="align_core", required=False,
101
+ help="Align and concatenate sequences for 'core' groups specified with '-w'.")
102
+ subparser.add_argument("-align_aa", action="store_true", required=False,
103
+ help="Align sequences as amino acids.")
104
+ subparser.add_argument("-no_gpa", action="store_false", dest="gene_presence_absence_out", required=False,
105
+ help="Skip creation of gene_presence_absence.csv.")
106
+ subparser.add_argument("-M", type=int, default=4000, dest="mem", required=False,
107
+ help="Memory allocation for clustering (MB) - CD-HIT parameter '-M'.")
108
+ subparser.add_argument("-T", type=int, default=8, dest="threads", required=False,
109
+ help="Number of threads for clustering/alignment - CD-HIT parameter '-T' | MAFFT parameter '--thread'.")
110
+
111
+ # Miscellaneous Arguments
112
+ subparser.add_argument("-verbose", action="store_true", required=False,
113
+ help="Print verbose output.")
114
+ subparser.add_argument("-v", "--version", action="version",
115
+ version=f"PyamilySeq {PyamilySeq_Version}: Exiting.", help="Print version number and exit.")
116
+
117
+ # Parse Arguments
118
+ options = parser.parse_args()
119
+
120
+ if options.write_groups != None and options.write_individual_groups == False:
121
+ options.write_individual_groups = True
122
+
123
+ # Example of conditional logic based on selected mode
124
+ print(f"Running PyamilySeq {PyamilySeq_Version} in {options.run_mode} mode:")
125
+ if options.run_mode == "Full" and options.verbose == True:
126
+ print("Processing Full mode with options:", vars(options))
127
+ elif options.run_mode == "Partial" and options.verbose == True:
128
+ print("Processing Partial mode with options:", vars(options))
129
+
130
+ ### Checking all required parameters are provided by user #!!# Doesn't seem to work
131
+ if options.run_mode == 'Full':
132
+ options.clustering_format = 'CD-HIT'
133
+ if getattr(options, 'reclustered', None) is not None:
134
+ sys.exit("Currently reclustering only works on Partial Mode.")
135
+ required_full_mode = [options.input_type, options.pident, options.len_diff]
136
+ if options.input_type != 'fasta':
137
+ required_full_mode.extend([options.input_dir, options.name_split_gff])
138
+ if all(required_full_mode):
139
+ # Proceed with the Full mode
140
+ pass
141
+ else:
142
+ missing_options = [opt for opt in
143
+ ['input_type', 'input_dir', 'name_split_gff', 'clustering_format', 'pident', 'len_diff'] if
144
+ not options.__dict__.get(opt)]
145
+ sys.exit(f"Missing required options for Full mode: {', '.join(missing_options)}")
146
+ if options.align_core:
147
+ options.write_individual_groups = True
148
+ if options.write_groups == None:
149
+ sys.exit('Must provide "-w" to output gene groups before alignment "-a" can be done.')
150
+ elif options.run_mode == 'Partial':
151
+ required_partial_mode = [options.cluster_file, options.original_fasta]
152
+ if all(required_partial_mode):
153
+ # Proceed with the Partial mode
154
+ pass
155
+ else:
156
+ missing_options = [opt for opt in
157
+ ['cluster_file','original_fasta'] if
158
+ not options.__dict__[opt]]
159
+ sys.exit(f"Missing required options for Partial mode: {', '.join(missing_options)}")
160
+ if options.align_core:
161
+ options.write_individual_groups = True
162
+ if options.write_groups == None or options.original_fasta == None:
163
+ sys.exit('Must provide "-w" to output gene groups before alignment "-a" can be done.')
164
+
165
+ if options.clustering_format == 'CD-HIT':
166
+ clust_affix = '.clstr'
167
+ elif options.clustering_format == 'TSV':
168
+ clust_affix = '.tsv'
169
+ elif options.clustering_format == 'CSV':
170
+ clust_affix = '.csv'
171
+
172
+ ###External tool checks:
173
+ ##MAFFT
174
+ if options.align_core == True:
175
+ if is_tool_installed('mafft'):
176
+ if options.verbose == True:
177
+ print("mafft is installed. Proceeding with alignment.")
178
+ else:
179
+ exit("mafft is not installed. Please install mafft to proceed.")
180
+ ##CD-HIT
181
+ if options.run_mode == 'Full':
182
+ if is_tool_installed('cd-hit'):
183
+ if options.verbose == True:
184
+ print("cd-hit is installed. Proceeding with clustering.")
185
+ if options.sequence_type == 'DNA':
186
+ clustering_mode = 'cd-hit-est'
187
+ elif options.sequence_type == 'AA':
188
+ clustering_mode = 'cd-hit'
189
+ if options.fast_mode == True:
190
+ options.fast_mode = 0
191
+ if options.verbose == True:
192
+ print("Running CD-HIT in fast mode.")
193
+ else:
194
+ options.fast_mode = 1
195
+ if options.verbose == True:
196
+ print("Running CD-HIT in slow mode.")
197
+ else:
198
+ exit("cd-hit is not installed. Please install cd-hit to proceed.")
199
+
200
+
201
+ # if options.write_groups != None and options.original_fasta == False:
202
+ # exit("-fasta must br provided if -w is used")
203
+
204
+ if hasattr(options, 'cluster_file') and options.cluster_file:
205
+ options.cluster_file = fix_path(options.cluster_file)
206
+ if hasattr(options, 'reclustered') and options.reclustered:
207
+ options.reclustered = fix_path(options.reclustered)
208
+ if hasattr(options, 'input_dir') and options.input_dir:
209
+ options.input_dir = fix_path(options.input_dir)
210
+ if hasattr(options, 'output_dir') and options.output_dir:
211
+ options.output_dir = fix_path(options.output_dir)
212
+
213
+ output_path = os.path.abspath(options.output_dir)
214
+ combined_out_file = os.path.join(output_path, "combined_sequences_dna.fasta")
215
+ clustering_output = os.path.join(output_path, 'clustering_' + options.clustering_format)
216
+
217
+ if options.group_mode == 'Species':
218
+ options.species_groups = options.species_groups + ',0'
219
+ groups_to_use = options.species_groups
220
+ elif options.group_mode == 'Genus':
221
+ options.genus_groups = options.genus_groups + ',>'
222
+ groups_to_use = options.genus_groups
223
+ if options.align_core != None:
224
+ sys.exit("-a align_core not a valid option in Genus mode.")
225
+
226
+
227
+ if options.run_mode == 'Full':
228
+ if options.clustering_format != 'CD-HIT':
229
+ sys.exit('Only CD-HIT clsutering works in Full Mode')
230
+
231
+ if not os.path.exists(output_path):
232
+ os.makedirs(output_path)
233
+ if options.sequence_type == 'AA':
234
+ clustering_mode = 'cd-hit'
235
+ file_to_cluster = combined_out_file.replace('_dna.fasta','_aa.fasta')
236
+ translate = True
237
+ elif options.sequence_type == 'DNA':
238
+ clustering_mode = 'cd-hit-est'
239
+ translate = False
240
+ file_to_cluster = combined_out_file
241
+ if options.input_type == 'separate':
242
+ read_separate_files(options.input_dir, options.name_split_gff, options.name_split_fasta, options.gene_ident, combined_out_file, translate)
243
+ run_cd_hit(options, file_to_cluster, clustering_output, clustering_mode)
244
+ elif options.input_type == 'combined':
245
+ read_combined_files(options.input_dir, options.name_split_gff, options.gene_ident, combined_out_file, translate)
246
+ run_cd_hit(options, file_to_cluster, clustering_output, clustering_mode)
247
+ elif options.input_type == 'fasta':
248
+ combined_out_file = options.input_fasta
249
+ ### FIX write code to detect if DNA or AA and if sequence tpye is AA then translate
250
+ # Detect if the input FASTA file contains DNA or AA sequences
251
+ is_dna = detect_sequence_type(options.input_fasta)
252
+ # If the sequence type is AA and the input is DNA, translate the DNA to AA
253
+ if options.sequence_type == 'AA' and is_dna:
254
+ translated_fasta = os.path.join(output_path, os.path.splitext(os.path.basename(options.input_fasta))[0] + '_aa.fasta')
255
+ translate_dna_to_aa(options.input_fasta, translated_fasta)
256
+ file_to_cluster = translated_fasta
257
+ else:
258
+ file_to_cluster = options.input_fasta
259
+ run_cd_hit(options, file_to_cluster, clustering_output, clustering_mode)
260
+
261
+
262
+ class clustering_options:
263
+ def __init__(self):
264
+ self.run_mode = options.run_mode
265
+ self.cluster_format = options.clustering_format
266
+ self.sequence_type = options.sequence_type
267
+ self.reclustered = None
268
+ self.sequence_tag = None
269
+ self.species_groups = groups_to_use
270
+ self.clusters = clustering_output + clust_affix
271
+ self.output_dir = options.output_dir
272
+ self.gene_presence_absence_out = options.gene_presence_absence_out
273
+ self.write_groups = options.write_groups
274
+ self.write_individual_groups = options.write_individual_groups
275
+ self.threads = options.threads
276
+ self.align_core = options.align_core
277
+ self.align_aa = options.align_aa
278
+ self.fasta = combined_out_file
279
+ self.verbose = options.verbose
280
+
281
+ clustering_options = clustering_options()
282
+
283
+ elif options.run_mode == 'Partial':
284
+ class clustering_options:
285
+ def __init__(self):
286
+ self.run_mode = options.run_mode
287
+ self.cluster_format = options.clustering_format
288
+ self.sequence_type = None
289
+ self.reclustered = options.reclustered
290
+ self.sequence_tag = options.sequence_tag
291
+ self.species_groups = groups_to_use
292
+ self.clusters = options.cluster_file
293
+ self.output_dir = options.output_dir
294
+ self.gene_presence_absence_out = options.gene_presence_absence_out
295
+ self.write_groups = options.write_groups
296
+ self.write_individual_groups = options.write_individual_groups
297
+ self.threads = options.threads
298
+ self.align_core = options.align_core
299
+ self.align_aa = options.align_aa
300
+ self.fasta = options.original_fasta
301
+ self.verbose = options.verbose
302
+
303
+ clustering_options = clustering_options()
304
+
305
+
306
+ if options.group_mode == 'Species':
307
+ species_cluster(clustering_options)
308
+ elif options.group_mode == 'Genus':
309
+ genus_cluster((clustering_options))
310
+
311
+
312
+ # Save arguments to a text file
313
+ with open(output_path+"/PyamilySeq_params.txt", "w") as outfile:
314
+ for arg, value in vars(options).items():
315
+ outfile.write(f"{arg}: {value}\n")
316
+
317
+ print("Thank you for using PyamilySeq -- A detailed user manual can be found at https://github.com/NickJD/PyamilySeq\n"
318
+ "Please report any issues to: https://github.com/NickJD/PyamilySeq/issues\n#####")
319
+
320
+ if __name__ == "__main__":
321
+ main()
@@ -0,0 +1,242 @@
1
+ #from line_profiler_pycharm import profile
2
+
3
+
4
+ try:
5
+ from .constants import *
6
+ from .clusterings import *
7
+ from .utils import *
8
+ except (ModuleNotFoundError, ImportError, NameError, TypeError) as error:
9
+ from constants import *
10
+ from clusterings import *
11
+ from utils import *
12
+
13
+
14
+ def gene_presence_absence_output(options, genus_dict, pangenome_clusters_First_sorted, pangenome_clusters_First_sequences_sorted):
15
+ print("Outputting gene_presence_absence file")
16
+ output_dir = os.path.abspath(options.output_dir)
17
+ #in_name = options.clusters.split('.')[0].split('/')[-1]
18
+ gpa_outfile = os.path.join(output_dir, 'gene_presence_absence.csv')
19
+ gpa_outfile = open(gpa_outfile, 'w')
20
+ gpa_outfile.write('"Gene","Non-unique Gene name","Annotation","No. isolates","No. sequences","Avg sequences per isolate","Genome Fragment","Order within Fragment","'
21
+ '"Accessory Fragment","Accessory Order with Fragment","QC","Min group size nuc","Max group size nuc","Avg group size nuc","')
22
+ gpa_outfile.write('","'.join(genus_dict.keys()))
23
+ gpa_outfile.write('"\n')
24
+ for cluster, sequences in pangenome_clusters_First_sequences_sorted.items():
25
+ average_sequences_per_genome = len(sequences) / len(pangenome_clusters_First_sorted[cluster])
26
+ gpa_outfile.write('"group_'+str(cluster)+'","","","'+str(len(pangenome_clusters_First_sorted[cluster]))+'","'+str(len(sequences))+'","'+str(average_sequences_per_genome)+
27
+ '","","","","","","","",""')
28
+
29
+
30
+ for genus in genus_dict.keys():
31
+ full_out = ''
32
+ tmp_list = []
33
+ for value in sequences:
34
+ if value.split('_')[0] == genus:
35
+ tmp_list.append(value)
36
+ if tmp_list:
37
+ full_out += ',"'+' '.join(tmp_list)+'"'
38
+ else:
39
+ full_out = ',""'
40
+ gpa_outfile.write(full_out)
41
+ gpa_outfile.write('\n')
42
+
43
+ ### Below is some unfinished code
44
+ # edge_list_outfile = open(in_name+'_edge_list.csv','w')
45
+ # for cluster, sequences in pangenome_clusters_First_sequences_sorted.items():
46
+ # output = []
47
+ # for entry in sequences:
48
+ # # Split each entry at '|'
49
+ # genome, gene = entry.split('|')
50
+ # # Format the result as "gene genome"
51
+ # output.append(f"{gene}\t{genome}")
52
+ # for line in output:
53
+ # edge_list_outfile.write(line + '\n')
54
+
55
+
56
+
57
+
58
+ def get_cores(options):
59
+ ##Calculate core groups
60
+ groups = OrderedDict()
61
+ cores = OrderedDict()
62
+ for group in options.core_groups.split(','):
63
+ first_core_group = 'First_genera_' + group
64
+ cores[first_core_group] = []
65
+ if options.reclustered != None:
66
+ extended_core_group = 'extended_genera_' + group
67
+ cores[extended_core_group] = []
68
+ combined_core_group = 'combined_genera_' + group
69
+ cores[combined_core_group] = []
70
+ second_core_group = 'Second_genera_' + group
71
+ cores[second_core_group] = []
72
+ only_second_core_group = 'only_Second_genera_' + group
73
+ cores[only_second_core_group] = []
74
+ return cores, groups
75
+
76
+ #@profile
77
+ def calc_First_only_core(cluster, First_num, cores):
78
+ try:
79
+ cores['First_genera_' + str(First_num)].append(cluster)
80
+ except KeyError:
81
+ cores['First_genera_>'].append(cluster)
82
+ #@profile
83
+ def calc_single_First_extended_Second_only_core(cluster, First_num, cores, Second_num): # Count gene families extended with StORFs
84
+ group = First_num + Second_num
85
+ try:
86
+ cores['extended_genera_' + str(group)].append(cluster)
87
+ except KeyError:
88
+ cores['extended_genera_>'].append(cluster)
89
+ #@profile
90
+ def calc_multi_First_extended_Second_only_core(cluster, First_num, cores, Second_num): # Count seperately those gene families extended with StORF_Reporter but combined >1 PEP
91
+ group = First_num + Second_num
92
+ try:
93
+ cores['combined_genera_' + str(group)].append(cluster)
94
+ except KeyError:
95
+ cores['combined_genera_>'].append(cluster)
96
+ #@profile
97
+ def calc_Second_only_core(cluster, cores, Second_num):
98
+ try:
99
+ cores['Second_genera_' + str(Second_num)].append(cluster)
100
+ except KeyError:
101
+ cores['Second_genera_>'].append(cluster)
102
+ #@profile
103
+ def calc_only_Second_only_core(cluster, cores, Second_num): # only count the true storf onlies
104
+ try:
105
+ cores['only_Second_genera_' + str(Second_num)].append(cluster)
106
+ except:
107
+ cores['only_Second_genera_>'].append(cluster)
108
+
109
+
110
+
111
+ #@profile
112
+ def cluster(options):
113
+
114
+ if options.cluster_format == 'CD-HIT':
115
+ genus_dict, pangenome_clusters_First, pangenome_clusters_First_genomes, pangenome_clusters_First_sequences, reps = cluster_CDHIT(options, '_')
116
+ elif 'TSV' in options.cluster_format or 'CSV' in options.cluster_format:
117
+ genus_dict, pangenome_clusters_First, pangenome_clusters_First_genomes, pangenome_clusters_First_sequences, reps = cluster_EdgeList(options, '_')
118
+
119
+ ###
120
+ cores, groups = get_cores(options)
121
+ ###
122
+
123
+ if options.reclustered != None:
124
+ if options.cluster_format == 'CD-HIT':
125
+ combined_pangenome_clusters_First_Second_clustered,not_Second_only_cluster_ids,combined_pangenome_clusters_Second, combined_pangenome_clusters_Second_sequences = combined_clustering_CDHIT(options, genus_dict, '_')
126
+ elif 'TSV' in options.cluster_format or 'CSV' in options.cluster_format:
127
+ combined_pangenome_clusters_First_Second_clustered,not_Second_only_cluster_ids,combined_pangenome_clusters_Second, combined_pangenome_clusters_Second_sequences = combined_clustering_Edge_List(options, '_')
128
+ pangenome_clusters_Type = combined_clustering_counting(options, pangenome_clusters_First, reps, combined_pangenome_clusters_First_Second_clustered, pangenome_clusters_First_genomes, '_')
129
+ else:
130
+ pangenome_clusters_Type = single_clustering_counting(pangenome_clusters_First, reps)
131
+
132
+
133
+
134
+ Number_Of_Second_Extending_But_Same_Genomes = 0
135
+
136
+ sorted_first_keys = sort_keys_by_values(pangenome_clusters_First, pangenome_clusters_First_sequences)
137
+ pangenome_clusters_First_sorted = reorder_dict_by_keys(pangenome_clusters_First, sorted_first_keys)
138
+ pangenome_clusters_First_sequences_sorted = reorder_dict_by_keys(pangenome_clusters_First_sequences, sorted_first_keys)
139
+ pangenome_clusters_Type_sorted = reorder_dict_by_keys(pangenome_clusters_Type, sorted_first_keys)
140
+
141
+ print("Calculating Groups")
142
+ seen_groupings = []
143
+ for cluster, numbers in pangenome_clusters_Type_sorted.items():
144
+ ############################### Calculate First only
145
+ cluster = str(cluster)
146
+ for grouping in numbers[2]: #!!# Could do with a more elegant solution
147
+ current_cluster = grouping[0].split(':')[0]
148
+ if current_cluster not in seen_groupings:
149
+ seen_groupings.append(current_cluster)
150
+ current_cluster_size = grouping[0].split(':')[1]
151
+ calc_First_only_core(current_cluster, current_cluster_size, cores)
152
+ ############################# Calculate First and Reclustered-Second
153
+ if numbers[0] == 1 and numbers[3] >= 1: # If Seconds did not combine First reps
154
+ calc_single_First_extended_Second_only_core(cluster, numbers[1], cores, numbers[3])
155
+ elif numbers[0] > 1 and numbers[3] >= 1: # If unique Seconds combined multiple Firsts
156
+ calc_multi_First_extended_Second_only_core(cluster, numbers[1], cores, numbers[3])
157
+ elif numbers[4] >= 1:
158
+ Number_Of_Second_Extending_But_Same_Genomes += 1
159
+ else:
160
+ if options.verbose == True:
161
+ print("First cluster " + current_cluster + " already processed - This is likely because it was clustered with another First representative.")
162
+
163
+ if options.reclustered != None:
164
+ combined_pangenome_clusters_ONLY_Second_Type = defaultdict(list)
165
+ combined_pangenome_clusters_Second_Type = defaultdict(list)
166
+ for cluster, genomes in combined_pangenome_clusters_Second.items():
167
+ if cluster in not_Second_only_cluster_ids:
168
+ combined_pangenome_clusters_Second_Type[cluster] = [cluster, len(genomes)]
169
+ else:
170
+ combined_pangenome_clusters_ONLY_Second_Type[cluster] = [cluster, len(genomes)]
171
+ for cluster, data in combined_pangenome_clusters_Second_Type.items():
172
+ if data[1] >= 1:
173
+ calc_Second_only_core(cluster, cores, data[1])
174
+ for cluster, data in combined_pangenome_clusters_ONLY_Second_Type.items():
175
+ if data[1] >= 1:
176
+ calc_only_Second_only_core(cluster, cores, data[1])
177
+ ###########################
178
+ ### Output
179
+ output_path = os.path.abspath(options.output_dir)
180
+ if not os.path.exists(output_path):
181
+ os.makedirs(output_path)
182
+ stats_out = os.path.join(output_path,'summary_statistics.txt')
183
+ key_order = list(cores.keys())
184
+ with open(stats_out,'w') as outfile:
185
+ print("Genus Groups:")
186
+ outfile.write("Genus Groups\n")
187
+ for key in key_order:
188
+ print(key+':\t'+str(len(cores[key])))
189
+ outfile.write(key + ':\t' + str(len(cores[key]))+'\n')
190
+ print("Total Number of First Gene Groups (Including Singletons): " + str(len(pangenome_clusters_First_sequences_sorted)))
191
+ outfile.write("Total Number of Gene Groups (Including Singletons): " + str(len(pangenome_clusters_First_sequences_sorted)))
192
+ if options.reclustered!= None:
193
+ print("Total Number of Second Gene Groups (Including Singletons): " + str(
194
+ len(combined_pangenome_clusters_Second_sequences)))
195
+ print("Total Number of First Gene Groups That Had Additional Second Sequences But Not New Genomes: " + str(
196
+ Number_Of_Second_Extending_But_Same_Genomes))
197
+ outfile.write("\nTotal Number of Second Gene Groups (Including Singletons): " + str(
198
+ len(combined_pangenome_clusters_Second_sequences)))
199
+ outfile.write("\nTotal Number of First Gene Groups That Had Additional Second Sequences But Not New Genomes: " + str(
200
+ Number_Of_Second_Extending_But_Same_Genomes))
201
+
202
+ if options.gene_presence_absence_out != False:
203
+ gene_presence_absence_output(options,genus_dict, pangenome_clusters_First_sorted, pangenome_clusters_First_sequences_sorted)
204
+
205
+ if options.run_mode == 'Full':
206
+ if options.reclustered == None:
207
+ combined_pangenome_clusters_Second_sequences = None
208
+ if options.write_groups != None:
209
+ print("Outputting gene group FASTA files")
210
+ sequences = read_fasta(options.fasta)
211
+ #output_dir = os.path.dirname(os.path.abspath(options.output_dir))
212
+ output_dir = os.path.join(options.output_dir, 'Gene_Groups_Output')
213
+ write_groups_func(options,output_dir, key_order, cores, sequences,
214
+ pangenome_clusters_First_sequences_sorted, combined_pangenome_clusters_Second_sequences)
215
+
216
+ elif options.run_mode == 'Partial':
217
+ if options.reclustered == None:
218
+ combined_pangenome_clusters_Second_sequences = None
219
+ if options.write_groups != None and options.fasta != None:
220
+ print("Outputting gene group FASTA files")
221
+ sequences = read_fasta(options.fasta)
222
+ #output_dir = os.path.dirname(os.path.abspath(options.output_dir))
223
+ output_dir = os.path.join(options.output_dir, 'Gene_Groups_Output')
224
+ write_groups_func(options,output_dir, key_order, cores, sequences,
225
+ pangenome_clusters_First_sequences_sorted, combined_pangenome_clusters_Second_sequences)
226
+
227
+
228
+ # if options.write_groups != None and options.fasta != None:
229
+ # sequences = read_fasta(options.fasta)
230
+ # output_dir = os.path.join(output_path, 'Gene_Families_Output')
231
+ #
232
+ # write_groups(options,output_dir, key_order, cores, sequences,
233
+ # pangenome_clusters_First_sequences_sorted, combined_pangenome_clusters_Second_sequences)
234
+
235
+
236
+ #!!# - Currently only align in Species Mode
237
+ #if options.align_core != None and options.fasta != None and options.write_groups != None:
238
+ # process_gene_families(options, os.path.join(output_path, 'Gene_Families_Output'), 'concatenated_genes_aligned.fasta')
239
+
240
+
241
+
242
+