Replidec 0.3.6__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.
Replidec/Replidec.py ADDED
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ # authors: sherry peng, torben sanders, erfan khamespanah
4
+ # mail: xue.peng@helmholtz-muenchen.de
5
+ # date: 2026.07.09
6
+
7
+ import re
8
+ import sys
9
+ from collections import defaultdict
10
+ import os
11
+ from subprocess import Popen, PIPE
12
+ import time
13
+ from Replidec.utility import mkdirs, checkEnv
14
+
15
+ # Master remote references pointing to static versioned database checkpoints
16
+ DATABASE_MANIFEST = {
17
+ "version": "0.3.3",
18
+ "url": "https://zenodo.org/records/22178169/files/db_v0.3.3.tgz",
19
+ "integrase_hmm": "db/integrase_pfv34.hmm",
20
+ "excisionase_hmm": "db/excisionase_pfv34.hmm",
21
+ "mmseqs_index": "db/bayes_mmseqs_index/training_prot_04_2025",
22
+ "scoring_matrix": "db/prokaryote_only_training_cluster_04_2025.stat.scoreOpt.tsv",
23
+ "inovirus_hmm": "db/Final_marker_morph.hmm",
24
+ "inovirus_blast": "db/Marker_ALV1"
25
+ }
26
+
27
+ def _get_timestamp():
28
+ """Generates standard tracking prefixes for log reporting."""
29
+ return time.strftime("[%Y-%m-%d %H:%M:%S]")
30
+
31
+
32
+ def runProdigal(inputseq, prefix, wd, program="meta", otherPara="-g 11"):
33
+ """
34
+ Runs Prodigal for gene calling.
35
+ Redirects stdout/stderr to an isolated log within the user's working directory.
36
+ """
37
+ checkEnv("prodigal")
38
+ mkdirs(wd)
39
+ log_file = os.path.join(wd, "external_tools.log")
40
+
41
+ cmd = "prodigal -i {0} {4} -a {2}/{1}.prodigal.gene.faa -d {2}/{1}.prodigal.gene.ffn -p {3} -f gff -o {2}/{1}.temp >> {5} 2>&1".format(
42
+ inputseq, prefix, wd, program, otherPara, log_file
43
+ )
44
+
45
+ print(f"{_get_timestamp()} [INFO] Running Prodigal: {prefix}")
46
+ obj = Popen(cmd, shell=True)
47
+ obj.wait()
48
+ return os.path.join(wd, f"{prefix}.prodigal.gene.faa")
49
+
50
+
51
+ def runHmmsearch(inputfile, prefix, wd, hmmModel, otherPara="--noali --cpu 1"):
52
+ """Executes target profile scans against reference hidden Markov databases."""
53
+ checkEnv("hmmsearch")
54
+ mkdirs(wd)
55
+ log_file = os.path.join(wd, "external_tools.log")
56
+ output_tbl = os.path.join(wd, f"{prefix}.hmmsearch.tblout")
57
+ output_out = os.path.join(wd, f"{prefix}.hmmsearch.out")
58
+
59
+ cmd = "hmmsearch {4} -o {5} --tblout {2} {3} {0} >> {1} 2>&1".format(
60
+ inputfile, log_file, output_tbl, hmmModel, otherPara, output_out
61
+ )
62
+
63
+ print(f"{_get_timestamp()} [INFO] Running HMMer search: {prefix}")
64
+ obj = Popen(cmd, shell=True)
65
+ obj.wait()
66
+ return output_tbl
67
+
68
+
69
+ def runMmseqsEasysearch(inputfile, prefix, wd, hmmDB,
70
+ otherPara="-s 7 --max-seqs 1 --alignment-mode 3 --alignment-output-mode 0 "
71
+ "--min-aln-len 40 --cov-mode 0 --greedy-best-hits 1 --threads 30"):
72
+ """Executes fast protein homology searches using MMseqs2."""
73
+ checkEnv("mmseqs")
74
+ mkdirs(wd)
75
+ log_file = os.path.join(wd, "external_tools.log")
76
+
77
+ tmpdir = os.path.join(wd, f"{prefix}_tmp")
78
+ mkdirs(tmpdir)
79
+ output = os.path.join(wd, f"{prefix}.mmseqs.m8")
80
+
81
+ cmd = "mmseqs easy-search {0} {1} {2} {3} {4} >> {5} 2>&1 && rm -rf {3}".format(
82
+ inputfile, hmmDB, output, tmpdir, otherPara, log_file
83
+ )
84
+
85
+ print(f"{_get_timestamp()} [INFO] Running MMseqs2 easy-search: {prefix}")
86
+ obj = Popen(cmd, shell=True)
87
+ obj.wait()
88
+ return output
89
+
90
+
91
+ def runBlastpsearch(inputfile, prefix, wd, db_prefix, evalue="1e-3", otherPara="-num_threads 3"):
92
+ """Standard BLASTp sequence alignment."""
93
+ checkEnv("blastp")
94
+ mkdirs(wd)
95
+ log_file = os.path.join(wd, "external_tools.log")
96
+ output = os.path.join(wd, f"{prefix}.blastp.m8")
97
+
98
+ cmd = "blastp -query {0} -db {1} -out {2} -outfmt 6 -evalue {3} {4} >> {5} 2>&1".format(
99
+ inputfile, db_prefix, output, evalue, otherPara, log_file
100
+ )
101
+
102
+ print(f"{_get_timestamp()} [INFO] Running BLASTp search: {prefix}")
103
+ obj = Popen(cmd, shell=True)
104
+ obj.wait()
105
+ return output
106
+
107
+
108
+ def load_scoreD(score_file):
109
+ """
110
+ Parses the pre-computed cluster conditional log-probability matrix.
111
+ Returns: Dict mapping a reference element to its [temperate_score, lytic_score].
112
+ """
113
+ d = {}
114
+ with open(score_file) as f:
115
+ for line in f:
116
+ ref, temperate, virulent, members = line.strip("\n").split("\t")
117
+ for member in members.split(";"):
118
+ d[member] = [temperate, virulent]
119
+ return d
120
+
121
+
122
+ def load_hmmsearch_opt(hmmsearch_opt, criteria=1e-5):
123
+ """
124
+ Parses sequential single-sample HMMER --tblout files.
125
+ """
126
+ annoD = defaultdict(dict)
127
+ if not os.path.exists(hmmsearch_opt):
128
+ return annoD
129
+ with open(hmmsearch_opt) as f:
130
+ for line in f:
131
+ if not line.startswith("#"):
132
+ t = re.split(r"\s+", line.strip("\n"))
133
+ if len(t) < 5:
134
+ continue
135
+ target_name, _, query_name, _, Evalue, *rest = t
136
+ bst_Evalue = rest[2] if len(rest) > 2 else Evalue
137
+ accession = t[3].split(".")[0]
138
+
139
+ if float(Evalue) <= float(criteria) and float(bst_Evalue) <= float(criteria):
140
+ annoD[target_name][accession] = query_name
141
+ return annoD
142
+
143
+
144
+ def load_hmmsearch_opt_batched(hmmsearch_opt, criteria=1e-5):
145
+ """
146
+ High-speed parser for batch/consolidated HMMER tabular outputs.
147
+ Extracts column 1 (target protein sequence identifiers) into an optimized set.
148
+ """
149
+ hit_proteins = set()
150
+ if not os.path.exists(hmmsearch_opt):
151
+ return hit_proteins
152
+
153
+ with open(hmmsearch_opt) as f:
154
+ for line in f:
155
+ if line.startswith("#"):
156
+ continue
157
+ t = re.split(r"\s+", line.strip("\n"))
158
+ if len(t) < 5:
159
+ continue
160
+
161
+ # Column mapping: t[0] yields target sequence ID, t[2] holds query matrix name
162
+ target_name, _, query_name, _, Evalue = t[:5]
163
+
164
+ if float(Evalue) <= float(criteria):
165
+ hit_proteins.add(target_name)
166
+
167
+ return hit_proteins
168
+
169
+
170
+ def load_m8_fmt_opt(m8_input, criteria=1e-5):
171
+ """
172
+ Parses blastp / mmseqs2 blast-style m8 (-outfmt 6) layout structures.
173
+ Filters by E-value and maps query sequences to their top hit reference IDs.
174
+ """
175
+ d = {}
176
+ if os.path.exists(m8_input):
177
+ with open(m8_input) as f:
178
+ for line in f:
179
+ parts = line.strip("\n").split("\t")
180
+ if len(parts) < 11:
181
+ continue
182
+ query, ref, _, _, _, _, _, _, _, _, evalue = parts[:11]
183
+ if float(evalue) <= criteria:
184
+ if query not in d or float(evalue) < float(d[query][-1]):
185
+ d[query] = [ref, evalue]
186
+ return d
187
+
188
+
189
+ def calculate_score(mmseqOpt, scoreD, criteria=1e-5):
190
+ """
191
+ Computes Naive Bayes joint conditional probabilities for lifestyles.
192
+ Combines log-likelihood values from homological alignments.
193
+ """
194
+ p_prior_temperate, p_prior_lytic = scoreD.get("Prior_probability", [0.0, 0.0])
195
+
196
+ p_temperate = [p_prior_temperate]
197
+ p_lytic = [p_prior_lytic]
198
+
199
+ d = load_m8_fmt_opt(mmseqOpt, criteria)
200
+ match_gene_number = len(d)
201
+
202
+ for query, values in d.items():
203
+ ref, evalue = values
204
+ if ref in scoreD:
205
+ p_temperate_gc, p_lytic_gc = scoreD[ref]
206
+ if float(p_temperate_gc) != 0 and float(p_lytic_gc) != 0:
207
+ p_temperate.append(p_temperate_gc)
208
+ p_lytic.append(p_lytic_gc)
209
+
210
+ label = "NA"
211
+ p_total_temperate, p_total_lytic = 0.0, 0.0
212
+ if len(p_temperate) == 1:
213
+ label = "Unclassified"
214
+ else:
215
+ for t, l in zip(p_temperate, p_lytic):
216
+ p_total_temperate += float(t)
217
+ p_total_lytic += float(l)
218
+
219
+ if p_total_temperate > p_total_lytic:
220
+ label = "Temperate"
221
+ elif p_total_temperate < p_total_lytic:
222
+ label = "Virulent"
223
+ else:
224
+ label = "Unclassified"
225
+
226
+ return p_total_temperate, p_total_lytic, label, match_gene_number
227
+
228
+
229
+ def inoviruses_PI_like_gene_search(inputfile, prefix, wd, hmmdb, blastdb_prefix, hmm_evalue="1e-3",
230
+ blastp_evalue="1e-3", blastp_para="-num_threads 3", hmmer_para="--noali --cpu 1"):
231
+ """Detects chronic Inovirus signature sequences using single-sample profiles."""
232
+ inovBlastpOpt = runBlastpsearch(inputfile, prefix, wd, blastdb_prefix, evalue=blastp_evalue, otherPara=blastp_para)
233
+ innoBlastD = load_m8_fmt_opt(inovBlastpOpt, float(blastp_evalue))
234
+
235
+ innoHmmerOpt = runHmmsearch(inputfile, prefix, wd, hmmdb, otherPara=hmmer_para)
236
+ innoHmmerD = load_hmmsearch_opt(innoHmmerOpt, float(hmm_evalue))
237
+
238
+ return bool(innoBlastD or innoHmmerD)
239
+
240
+
241
+ def check_db_md5(db_dir):
242
+ """Validates the local database files against official MD5 checksum targets."""
243
+ cmd = "cd %s/db && md5sum --check md5sum.list" % (db_dir)
244
+ obj = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
245
+ obj.wait()
246
+ out, err = obj.communicate()
247
+ if b"FAILED" in out or b"FAILED" in err:
248
+ print(f"{_get_timestamp()} [WARNING] Database validation failure detected! Re-download recommended.")
249
+ sys.exit(1)
250
+ else:
251
+ print(f"{_get_timestamp()} [INFO] Database md5 integrity checks: PASSED")
252
+
253
+
254
+ def checkdb_and_download(scriptPos, redownload=False):
255
+ """Ensures reference database components are present and structurally valid."""
256
+ if redownload:
257
+ if os.path.exists(os.path.join(scriptPos, "db")):
258
+ cmd = f"rm -rf {os.path.join(scriptPos, 'discarded_db')} && mv -f {os.path.join(scriptPos, 'db')} {os.path.join(scriptPos, 'discarded_db')}"
259
+ obj = Popen(cmd, shell=True)
260
+ obj.wait()
261
+
262
+ if not os.path.exists(os.path.join(scriptPos, "db")):
263
+ print(f"{_get_timestamp()} [INFO] Reference database missing. Downloading dependencies...")
264
+ url = DATABASE_MANIFEST["url"]
265
+ file = os.path.split(url)[-1]
266
+ cmd = "wget {0} -P {1} && cd {1} && tar -zxvf {2} && rm -rf {2}".format(url, scriptPos, file)
267
+
268
+ try:
269
+ obj = Popen(cmd, shell=True)
270
+ obj.wait()
271
+ check_db_md5(scriptPos)
272
+ except Exception as e:
273
+ print(f"{_get_timestamp()} [CRITICAL] Database setup failed: {str(e)}. Rerun with -d.")
274
+ sys.exit(1)
275
+ else:
276
+ print(f"{_get_timestamp()} [INFO] Database verified.")
277
+
278
+
279
+ def bayes_classifier_single(inputfile, prefix, wd, hmm_criteria=1e-5, mmseqs_criteria=1e-5, blastp_criteria=1e-3,
280
+ blastp_para="-num_threads 3", hmmer_para="--noali --cpu 3",
281
+ mmseqs_para="-s 7 --max-seqs 1 --alignment-mode 3 --alignment-output-mode 0 --min-aln-len 40 --cov-mode 0 --greedy-best-hits 1 --threads 3"):
282
+ """
283
+ Processes single-sample lifestyle predictions.
284
+ Retained to support alternative processing workflows.
285
+ """
286
+ mkdirs(wd)
287
+ fileDir = os.path.dirname(os.path.abspath(__file__))
288
+
289
+ # --- Step 1: Pre-Screen for Chronic Markers to Short-Circuit Early ---
290
+ inno_hmmDB = os.path.join(fileDir, "db/Final_marker_morph.hmm")
291
+ inno_blastPre = os.path.join(fileDir, "db/Marker_ALV1")
292
+ inno_dect_wd = os.path.join(wd, "BC_Inno")
293
+
294
+ inno_res = inoviruses_PI_like_gene_search(
295
+ inputfile, f"{prefix}.Inno", inno_dect_wd, inno_hmmDB, inno_blastPre,
296
+ hmm_evalue=blastp_criteria, blastp_evalue=blastp_criteria, blastp_para=blastp_para, hmmer_para=hmmer_para
297
+ )
298
+
299
+ if inno_res:
300
+ # Short circuit: return NA's for scores and "Skipped" for all structural labels, assigning "Chronic" as final label
301
+ return [prefix, "NA", "NA", "Skipped", "NA", "NA", "Skipped", "Chronic", "NA"]
302
+
303
+ # --- Step 2: Proceed With Structural Alignments if Not Chronic ---
304
+ pfam_label, bc_label, final_label = "Unclassified", "Unclassified", "Unclassified"
305
+ integrase_hmm = os.path.join(fileDir, "db/integrase_pfv34.hmm")
306
+ excisionase_hmm = os.path.join(fileDir, "db/excisionase_pfv34.hmm")
307
+
308
+ pfam_wd = os.path.join(wd, "BC_pfam")
309
+ inte_opt = runHmmsearch(inputfile, f"{prefix}.BC_integrase", pfam_wd, integrase_hmm, hmmer_para)
310
+ excision_opt = runHmmsearch(inputfile, f"{prefix}.BC_excisionase", pfam_wd, excisionase_hmm, hmmer_para)
311
+
312
+ inte_annoD = load_hmmsearch_opt(inte_opt, criteria=hmm_criteria)
313
+ excision_annoD = load_hmmsearch_opt(excision_opt, criteria=hmm_criteria)
314
+
315
+ inte_label = len(inte_annoD) if inte_annoD else 0
316
+ excision_label = len(excision_annoD) if excision_annoD else 0
317
+
318
+ if inte_label or excision_label:
319
+ pfam_label = "Temperate"
320
+ else:
321
+ pfam_label = "Virulent"
322
+
323
+ bc_mmseqsDB = os.path.join(fileDir, "db/bayes_mmseqs_index/training_prot_04_2025")
324
+ mmseqs_wd = os.path.join(wd, "BC_mmseqs")
325
+ mmseq_opt = runMmseqsEasysearch(inputfile, f"{prefix}.BC_mmseqs", mmseqs_wd, bc_mmseqsDB, otherPara=mmseqs_para)
326
+
327
+ score_file = os.path.join(fileDir, "db/prokaryote_only_training_cluster_04_2025.stat.scoreOpt.tsv")
328
+ member2scoreD = load_scoreD(score_file)
329
+
330
+ p_total_temperate, p_total_lytic, bc_label, match_gene_number = calculate_score(mmseq_opt, member2scoreD,
331
+ criteria=mmseqs_criteria)
332
+
333
+ if pfam_label == "Temperate" or bc_label == "Temperate":
334
+ final_label = "Temperate"
335
+ elif pfam_label == "Virulent" and bc_label == "Unclassified":
336
+ final_label = "Virulent"
337
+ else:
338
+ final_label = "Virulent"
339
+
340
+ return [prefix, inte_label, excision_label, pfam_label, p_total_temperate, p_total_lytic, bc_label, final_label,
341
+ match_gene_number]
342
+
343
+
344
+ if __name__ == "__main__":
345
+ scriptPos = os.path.dirname(os.path.abspath(__file__))
346
+ checkdb_and_download(scriptPos)
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ # authors: sherry peng, torben sanders, erfan khamespanah
4
+ # date: 2026.07.09
5
+
6
+ import os
7
+ import sys
8
+ from argparse import RawTextHelpFormatter, ArgumentParser
9
+
10
+ # Inject parent context directory into sys path to avoid local context resolution issues
11
+ sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
12
+
13
+ from Replidec.Replidec_multi import bayes_classifier_batch, bayes_classifier_contig, bayes_classifier_genomes
14
+ from Replidec.Replidec import checkdb_and_download
15
+ from Replidec import __version__
16
+
17
+ def main():
18
+ """Main execution entry point parsing user flags and routing calls."""
19
+ parser = ArgumentParser(
20
+ description="Replidec: Replication cycle prediction tool for prokaryotic viruses",
21
+ formatter_class=RawTextHelpFormatter
22
+ )
23
+ parser.add_argument("-v", "--version", action='version', version=f'Replidec v{__version__}')
24
+
25
+ # Fixes an earlier issue by enabling required=False so that database maintenance runs freely without an input file
26
+ parser.add_argument("-p", "--program", default='multi_fasta', required=False,
27
+ choices=['multi_fasta', 'genome_table', 'protein_table'],
28
+ metavar="",
29
+ help=(
30
+ "{ multi_fasta | genome_table | protein_table }\n\n"
31
+ "multi_fasta mode:\n"
32
+ " input is a fasta file and treats each sequence as one virus\n\n"
33
+ "genome_table mode:\n"
34
+ " input is a tab-separated file [1st col: sample name, 2nd col: genome fasta path]\n\n"
35
+ "protein_table mode:\n"
36
+ " input is a tab-separated file [1st col: sample name, 2nd col: protein faa path]\n"
37
+ ))
38
+
39
+ parser.add_argument("-i", "--input_file", metavar="", default=None,
40
+ help="The input file, which can be a sequence file or an index table\n")
41
+
42
+ parser.add_argument("-w", "--work_dir", default="replidec_results", metavar="", dest="working_directory",
43
+ help="Directory to store intermediate and final results (default = ./replidec_results)")
44
+
45
+ parser.add_argument("-n", "--file_name", default="prediction_summary.tsv", metavar="", dest="file_name",
46
+ help="Name of final summary file (default = prediction_summary.tsv)")
47
+
48
+ parser.add_argument("-t", "--threads", default=4, type=int, metavar="",
49
+ help="Number of compute threads assigned to execution (default = 4)")
50
+
51
+ parser.add_argument("-e", "--hmmer_Eval", default=1e-5, type=float, metavar="", dest="hmmer_Evalue_threshold",
52
+ help="E-value threshold to filter hmmer results (default = 1e-5)")
53
+
54
+ parser.add_argument("-E", "--hmmer_parameters", default="--noali", metavar="", dest="hmmer_parameters",
55
+ help="Parameters used for hmmer execution (default = --noali)")
56
+
57
+ parser.add_argument("-m", "--mmseq_Eval", default=1e-5, type=float, metavar="", dest="mmseqs_Evalue_threshold",
58
+ help="E-value threshold to filter mmseqs2 results (default = 1e-5)")
59
+
60
+ parser.add_argument("-M", "--mmseq_parameters", dest="mmseqs_parameters", metavar="",
61
+ default="-s 7 --max-seqs 1 --alignment-mode 3 --alignment-output-mode 0 --min-aln-len 40 --cov-mode 0 --greedy-best-hits 1",
62
+ help="Parameters used for mmseqs2 alignment processes")
63
+
64
+ parser.add_argument("-b", "--blastp_Eval", default=1e-5, type=float, metavar="", dest="blastp_Evalue_threshold",
65
+ help="E-value threshold to filter blastp results (default = 1e-5)")
66
+
67
+ parser.add_argument("-B", "--blastp_parameter", default="", metavar="", dest="blastp_parameters",
68
+ help="Parameters used for blastp alignment processes")
69
+
70
+ parser.add_argument("-d", "--db_redownload", action='store_true', default=False, dest="db_redownload",
71
+ help="(Re-)download reference database")
72
+
73
+ args = parser.parse_args()
74
+ fileDir = os.path.dirname(os.path.abspath(__file__))
75
+
76
+ # Process immediate database installation if requested
77
+ if args.db_redownload:
78
+ print("[INFO] Initiating direct database deployment and verification routines...")
79
+ checkdb_and_download(fileDir, redownload=True)
80
+ if not args.input_file:
81
+ print("[SUCCESS] Database routine finalized successfully. Exiting cleanly.")
82
+ sys.exit(0)
83
+
84
+ # Validate that an input file is provided for analysis runs
85
+ if not args.input_file:
86
+ parser.error(
87
+ "the following arguments are required: -i/--input_file (unless only maintaining database environments via -d)")
88
+
89
+ print(f"[INFO] Initializing processing pipelines utilizing execution framework: {args.program}")
90
+
91
+ # Route execution based on chosen program mode
92
+ if args.program == "genome_table":
93
+ bayes_classifier_genomes(args.input_file, args.working_directory,
94
+ summaryfile=args.file_name, threads=args.threads,
95
+ hmm_criteria=args.hmmer_Evalue_threshold, mmseqs_criteria=args.mmseqs_Evalue_threshold,
96
+ blastp_criteria=args.blastp_Evalue_threshold,
97
+ hmmer_para=args.hmmer_parameters, mmseqs_para=args.mmseqs_parameters,
98
+ blastp_para=args.blastp_parameters)
99
+
100
+ elif args.program == "multi_fasta":
101
+ bayes_classifier_contig(args.input_file, args.working_directory,
102
+ summaryfile=args.file_name, threads=args.threads,
103
+ hmm_criteria=args.hmmer_Evalue_threshold, mmseqs_criteria=args.mmseqs_Evalue_threshold,
104
+ blastp_criteria=args.blastp_Evalue_threshold,
105
+ hmmer_para=args.hmmer_parameters, mmseqs_para=args.mmseqs_parameters,
106
+ blastp_para=args.blastp_parameters)
107
+
108
+ elif args.program == "protein_table":
109
+ bayes_classifier_batch(args.input_file, args.working_directory,
110
+ summaryfile=args.file_name, threads=args.threads,
111
+ hmm_criteria=args.hmmer_Evalue_threshold, mmseqs_criteria=args.mmseqs_Evalue_threshold,
112
+ blastp_criteria=args.blastp_Evalue_threshold,
113
+ hmmer_para=args.hmmer_parameters, mmseqs_para=args.mmseqs_parameters,
114
+ blastp_para=args.blastp_parameters)
115
+
116
+
117
+ if __name__ == "__main__":
118
+ main()
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ # authors: sherry peng, torben sanders, erfan khamespanah
4
+ # date: 2026.07.09
5
+
6
+ import os
7
+ import re
8
+ from concurrent.futures import ThreadPoolExecutor, as_completed
9
+ from Bio import SeqIO
10
+
11
+ # Inherit the core alignment execution stations, timestamping engines, and global database manifests
12
+ from Replidec.Replidec import (
13
+ DATABASE_MANIFEST,
14
+ _get_timestamp,
15
+ checkdb_and_download,
16
+ runProdigal,
17
+ runHmmsearch,
18
+ runMmseqsEasysearch,
19
+ runBlastpsearch,
20
+ load_scoreD,
21
+ load_hmmsearch_opt_batched,
22
+ load_m8_fmt_opt,
23
+ bayes_classifier_single
24
+ )
25
+ from Replidec.utility import mkdirs
26
+
27
+
28
+ def bayes_classifier_contig(inputfile, wd, summaryfile="BC_predict.summary", threads=4,
29
+ hmm_criteria=1e-5, mmseqs_criteria=1e-5, blastp_criteria=1e-5,
30
+ blastp_para="", hmmer_para="", mmseqs_para=""):
31
+ '''
32
+ Aim: High-speed single-pass batch prediction for multi-FASTA contig files.
33
+ '''
34
+ print(f"{_get_timestamp()} [INFO] Initializing reference database verification...")
35
+ fileDir = os.path.dirname(os.path.abspath(__file__))
36
+ checkdb_and_download(fileDir)
37
+
38
+ if not inputfile:
39
+ print(f"{_get_timestamp()} [CRITICAL] No input file provided.")
40
+ return
41
+ if not os.path.exists(inputfile):
42
+ raise FileNotFoundError(f"Input file '{inputfile}' does not exist.")
43
+
44
+ mkdirs(wd)
45
+
46
+ # --- Thread Injection Logic ---
47
+ if '--threads' in mmseqs_para:
48
+ mmseqs_para = re.sub(r'--threads\s+\d+', f'--threads {threads}', mmseqs_para)
49
+ else:
50
+ mmseqs_para += f' --threads {threads}'
51
+
52
+ if '--cpu' in hmmer_para:
53
+ hmmer_para = re.sub(r'--cpu\s+\d+', f'--cpu {threads}', hmmer_para)
54
+ else:
55
+ hmmer_para += f' --cpu {threads}'
56
+
57
+ if '-num_threads' in blastp_para:
58
+ blastp_para = re.sub(r'-num_threads\s+\d+', f'-num_threads {threads}', blastp_para)
59
+ else:
60
+ blastp_para += f' -num_threads {threads}'
61
+
62
+ # --- Phase 1: Metagenomic Gene Prediction ---
63
+ print(f"{_get_timestamp()} [INFO] Sanitizing multi-FASTA boundaries and running Prodigal...")
64
+ sanitized_fasta = os.path.join(wd, "all_contigs_sanitized.fna")
65
+ contig_lengths = {}
66
+
67
+ with open(sanitized_fasta, "w") as out_f:
68
+ for record in SeqIO.parse(inputfile, "fasta"):
69
+ clean_id = record.id.replace("|", "_")
70
+ record.id = clean_id
71
+ record.description = ""
72
+ contig_lengths[clean_id] = len(record.seq)
73
+ SeqIO.write(record, out_f, "fasta")
74
+
75
+ prodigal_wd = os.path.join(wd, "BC_prodigal")
76
+ master_faa = runProdigal(sanitized_fasta, "all_contigs", prodigal_wd, program="meta", otherPara="-g 11")
77
+
78
+ if not os.path.exists(master_faa) or os.path.getsize(master_faa) == 0:
79
+ print(f"{_get_timestamp()} [WARNING] No proteins predicted. Exiting workflow cleanly.")
80
+ return
81
+
82
+ protein_to_contig = {}
83
+ contig_to_proteins = {cid: [] for cid in contig_lengths.keys()}
84
+ for record in SeqIO.parse(master_faa, "fasta"):
85
+ last_underscore = record.id.rfind("_")
86
+ if last_underscore != -1:
87
+ contig_id = record.id[:last_underscore]
88
+ if contig_id in contig_to_proteins:
89
+ protein_to_contig[record.id] = contig_id
90
+ contig_to_proteins[contig_id].append(record.id)
91
+
92
+ # --- Phase 2: Vectorized Chronic Inovirus Marker Screening (Moved Up) ---
93
+ print(f"{_get_timestamp()} [INFO] Phase 2: Screening for Chronic Inovirus elements...")
94
+ inno_wd = os.path.join(wd, "BC_Inno")
95
+ inno_hmmDB = os.path.join(fileDir, DATABASE_MANIFEST["inovirus_hmm"])
96
+ inno_blastPre = os.path.join(fileDir, DATABASE_MANIFEST["inovirus_blast"])
97
+
98
+ inno_blast_opt = runBlastpsearch(master_faa, "all_contigs.Inno", inno_wd, inno_blastPre,
99
+ evalue=str(blastp_criteria), otherPara=blastp_para)
100
+ inno_blast_hits = load_m8_fmt_opt(inno_blast_opt, float(blastp_criteria))
101
+
102
+ inno_hmm_opt = runHmmsearch(master_faa, "all_contigs.Inno", inno_wd, inno_hmmDB, hmmer_para)
103
+ inno_hmm_hits = load_hmmsearch_opt_batched(inno_hmm_opt, float(blastp_criteria))
104
+
105
+ # Identify contigs that hit as Chronic
106
+ chronic_contigs = set()
107
+ for contig_id, proteins in contig_to_proteins.items():
108
+ if any(p in inno_blast_hits or p in inno_hmm_hits for p in proteins):
109
+ chronic_contigs.add(contig_id)
110
+
111
+ # Short-circuit logic: create a filtered protein FASTA containing only non-chronic targets
112
+ filtered_faa = os.path.join(prodigal_wd, "non_chronic_contigs.faa")
113
+ non_chronic_count = 0
114
+ with open(filtered_faa, "w") as out_f:
115
+ for record in SeqIO.parse(master_faa, "fasta"):
116
+ contig_id = protein_to_contig.get(record.id)
117
+ if contig_id not in chronic_contigs:
118
+ SeqIO.write(record, out_f, "fasta")
119
+ non_chronic_count += 1
120
+
121
+ # --- Phase 3: Vectorized Alignment against Structural Profiles ---
122
+ inte_anno, excision_anno, mmseq_hits = {}, {}, {}
123
+
124
+ if non_chronic_count > 0:
125
+ print(f"{_get_timestamp()} [INFO] Phase 3: Launching HMMER and MMseqs2 structural profiles for remaining contigs...")
126
+ pfam_wd = os.path.join(wd, "BC_pfam")
127
+ integrase_hmm = os.path.join(fileDir, DATABASE_MANIFEST["integrase_hmm"])
128
+ excisionase_hmm = os.path.join(fileDir, DATABASE_MANIFEST["excisionase_hmm"])
129
+
130
+ # Run tools strictly on the filtered, non-chronic FASTA
131
+ inte_opt = runHmmsearch(filtered_faa, "all_contigs.BC_integrase", pfam_wd, integrase_hmm, hmmer_para)
132
+ excision_opt = runHmmsearch(filtered_faa, "all_contigs.BC_excisionase", pfam_wd, excisionase_hmm, hmmer_para)
133
+
134
+ inte_anno = load_hmmsearch_opt_batched(inte_opt, criteria=hmm_criteria)
135
+ excision_anno = load_hmmsearch_opt_batched(excision_opt, criteria=hmm_criteria)
136
+
137
+ bc_mmseqsDB = os.path.join(fileDir, DATABASE_MANIFEST["mmseqs_index"])
138
+ mmseqs_wd = os.path.join(wd, "BC_mmseqs")
139
+ mmseq_opt = runMmseqsEasysearch(filtered_faa, "all_contigs.BC_mmseqs", mmseqs_wd, bc_mmseqsDB, otherPara=mmseqs_para)
140
+ mmseq_hits = load_m8_fmt_opt(mmseq_opt, criteria=mmseqs_criteria)
141
+
142
+ score_file = os.path.join(fileDir, DATABASE_MANIFEST["scoring_matrix"])
143
+ score_matrix = load_scoreD(score_file)
144
+ p_prior_temperate, p_prior_lytic = score_matrix.get("Prior_probability", [0.0, 0.0])
145
+
146
+ # --- Phase 4: Scoring Matrix Assembly & Final Report Output ---
147
+ print(f"{_get_timestamp()} [INFO] Assembling scoring matrices and writing global execution summary...")
148
+ summary_path = os.path.join(wd, summaryfile)
149
+
150
+ total_contigs = len(contig_lengths)
151
+ processed_count = 0
152
+
153
+ with open(summary_path, "w") as opt:
154
+ header = "sample_name\tintegrase_number\texcisionase_number\tpfam_label\tbc_temperate\tbc_virulent\tbc_label\tfinal_label\tmatch_gene_number\n"
155
+ opt.write(header)
156
+
157
+ for contig_id in contig_lengths.keys():
158
+ # Apply hard overrides to Chronic short-circuited contigs
159
+ if contig_id in chronic_contigs:
160
+ row = [contig_id, "NA", "NA", "Skipped", "NA", "NA", "Skipped", "Chronic", "NA"]
161
+ opt.write("\t".join(str(x) for x in row) + "\n")
162
+ processed_count += 1
163
+ continue
164
+
165
+ # Process remaining non-chronic contigs normally
166
+ proteins = contig_to_proteins[contig_id]
167
+
168
+ inte_count = sum(1 for p in proteins if p in inte_anno)
169
+ excision_count = sum(1 for p in proteins if p in excision_anno)
170
+ pfam_label = "Temperate" if (inte_count > 0 or excision_count > 0) else "Virulent"
171
+
172
+ p_temperate = [p_prior_temperate]
173
+ p_lytic = [p_prior_lytic]
174
+ match_gene_number = 0
175
+
176
+ for p in proteins:
177
+ if p in mmseq_hits:
178
+ match_gene_number += 1
179
+ ref, _ = mmseq_hits[p]
180
+ if ref in score_matrix:
181
+ pt, pl = score_matrix[ref]
182
+ if float(pt) != 0 and float(pl) != 0:
183
+ p_temperate.append(pt)
184
+ p_lytic.append(pl)
185
+
186
+ if len(p_temperate) == 1:
187
+ p_total_temperate, p_total_lytic = 0.0, 0.0
188
+ bc_label = "Unclassified"
189
+ else:
190
+ p_total_temperate = sum(float(t) for t in p_temperate)
191
+ p_total_lytic = sum(float(l) for l in p_lytic)
192
+ if p_total_temperate > p_total_lytic:
193
+ bc_label = "Temperate"
194
+ elif p_total_temperate < p_total_lytic:
195
+ bc_label = "Virulent"
196
+ else:
197
+ bc_label = "Unclassified"
198
+
199
+ if pfam_label == "Temperate" or bc_label == "Temperate":
200
+ final_label = "Temperate"
201
+
202
+ elif pfam_label == "Virulent" and bc_label == "Unclassified":
203
+ final_label = "Virulent"
204
+
205
+ else:
206
+ final_label = "Virulent"
207
+
208
+ row = [contig_id, inte_count, excision_count, pfam_label, p_total_temperate, p_total_lytic, bc_label,
209
+ final_label, match_gene_number]
210
+ opt.write("\t".join(str(x) for x in row) + "\n")
211
+
212
+ processed_count += 1
213
+ if processed_count % 5000 == 0 or processed_count == total_contigs:
214
+ print(f"{_get_timestamp()} [PROGRESS] Evaluated {processed_count:,} / {total_contigs:,} contigs.")
215
+
216
+ print(f"{_get_timestamp()} [SUCCESS] Job finalized cleanly. Summary written to: {summary_path}")
217
+
218
+
219
+ def bayes_classifier_batch(inputfile, wd, summaryfile="BC_predict.summary", threads=10,
220
+ hmm_criteria=1e-5, mmseqs_criteria=1e-5, blastp_criteria=1e-5,
221
+ blastp_para="", hmmer_para="", mmseqs_para=""):
222
+ '''
223
+ Aim: Multithreaded engine execution for index data tables mapping pre-computed protein FAA listings.
224
+ '''
225
+ fileDir = os.path.dirname(os.path.abspath(__file__))
226
+ checkdb_and_download(fileDir)
227
+ mkdirs(wd)
228
+
229
+ with open(os.path.join(wd, summaryfile), "w") as opt:
230
+ header = "sample_name\tintegrase_number\texcisionase_number\tpfam_label\tbc_temperate\tbc_virulent\tbc_label\tfinal_label\tmatch_gene_number\tpath\n"
231
+ opt.write(header)
232
+
233
+ executor = ThreadPoolExecutor(max_workers=threads)
234
+ all_task = []
235
+ kwargsD = {"hmm_criteria": hmm_criteria, "mmseqs_criteria": mmseqs_criteria, "blastp_criteria": blastp_criteria,
236
+ "blastp_para": blastp_para, "hmmer_para": hmmer_para, "mmseqs_para": mmseqs_para}
237
+
238
+ with open(inputfile) as f:
239
+ for line in f:
240
+ sample_name, path = line.strip("\n").split("\t")
241
+ all_task.append(executor.submit(bayes_classifier_single, path, sample_name, wd, **kwargsD))
242
+
243
+ for future in as_completed(all_task):
244
+ res = future.result()
245
+ res.append(path)
246
+ opt.write("\t".join([str(i) for i in res]) + "\n")
247
+
248
+
249
+ def bayes_classifier_genomes(inputfile, wd, summaryfile="BC_predict.summary", threads=10,
250
+ hmm_criteria=1e-5, mmseqs_criteria=1e-5, blastp_criteria=1e-5,
251
+ blastp_para="", hmmer_para="", mmseqs_para=""):
252
+ '''
253
+ Aim: Multithreaded engine execution for structured tables mapping whole genome assembly configurations.
254
+ '''
255
+ fileDir = os.path.dirname(os.path.abspath(__file__))
256
+ checkdb_and_download(fileDir)
257
+ mkdirs(wd)
258
+
259
+ with open(os.path.join(wd, summaryfile), "w") as opt:
260
+ header = "sample_name\tintegrase_number\texcisionase_number\tpfam_label\tbc_temperate\tbc_virulent\tbc_label\tfinal_label\tmatch_gene_number\tpath\n"
261
+ opt.write(header)
262
+
263
+ executor = ThreadPoolExecutor(max_workers=threads)
264
+ all_task = []
265
+ kwargsD = {"hmm_criteria": hmm_criteria, "mmseqs_criteria": mmseqs_criteria, "blastp_criteria": blastp_criteria,
266
+ "blastp_para": blastp_para, "hmmer_para": hmmer_para, "mmseqs_para": mmseqs_para}
267
+ faaDict = {}
268
+
269
+ with open(inputfile) as f:
270
+ for line in f:
271
+ sample_name, path = line.strip("\n").split("\t")
272
+ faaFile = runProdigal(path, sample_name, f"{wd}/BC_prodigal", program="meta", otherPara="-g 11")
273
+ faaDict[sample_name] = faaFile
274
+
275
+ for sample_name, faaFile in faaDict.items():
276
+ if os.path.getsize(faaFile) != 0:
277
+ all_task.append(executor.submit(bayes_classifier_single, faaFile, sample_name, wd, **kwargsD))
278
+
279
+ for future in as_completed(all_task):
280
+ res = future.result()
281
+ faaFile = faaDict[res[0]]
282
+ res.append(faaFile)
283
+ opt.write("\t".join([str(i) for i in res]) + "\n")
284
+
285
+
286
+ if __name__ == "__main__":
287
+ pass
Replidec/__init__.py ADDED
@@ -0,0 +1,18 @@
1
+ """
2
+
3
+ Replidec package - bacteriophage lifestyle prediction (temperate|virulent|chronic) based on the naive bayes classifier.
4
+
5
+ Written by Xue Peng (Email: xue.peng@helmholtz-muenchen.de)
6
+
7
+ Python modules
8
+ ----------------
9
+ The package consists of the following Python modules:
10
+ * bayes_classifier_batch
11
+ * bayes_classifier_contig
12
+ * bayes_classifier_genomes
13
+
14
+ """
15
+
16
+ __author__ = """Xue Peng, Torben Sanders, Erfan Khamespanah"""
17
+ __email__ = 'xue.peng@helmholtz-muenchen.de'
18
+ __version__ = '0.3.6'
Replidec/utility.py ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+ # authors: sherry peng, torben sanders, erfan khamespanah
4
+ # mail: xue.peng@helmholtz-muenchen.de
5
+ # date: 2026.07.09
6
+
7
+ import os
8
+ from subprocess import Popen, PIPE
9
+ import sys
10
+
11
+
12
+ def mkdirs(dirname):
13
+ '''Safely creates output directories if they do not already exist.'''
14
+ if not os.path.exists(dirname):
15
+ os.makedirs(dirname)
16
+
17
+
18
+ def checkEnv(sft):
19
+ '''
20
+ Validates that required third-party binaries are accessible in the system PATH.
21
+
22
+ Uses standard system commands to check for binary availability. If a dependency
23
+ cannot be resolved, it raises a clean critical alert and exits to prevent
24
+ pipeline failures down the line.
25
+ '''
26
+ cmd = "which %s" % sft
27
+
28
+ # Direct stdout and stderr streams to execution PIPEs to preserve clean console logging
29
+ obj = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
30
+ obj.wait()
31
+
32
+ # An execution return code other than 0 indicates missing dependencies
33
+ if obj.returncode != 0:
34
+ print(f"\n[CRITICAL] Dependency Missing: '{sft}' could not be resolved in your current environment.")
35
+ print(f"Please ensure '{sft}' is installed and available in your PATH variable.\n")
36
+ sys.exit(1)
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022, Xue Peng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.1
2
+ Name: Replidec
3
+ Version: 0.3.6
4
+ Summary: Replication Cycle Decipher for Phages
5
+ Home-page: https://github.com/pengSherryYel/Replidec
6
+ Author: Xue Peng
7
+ Author-email: peng_sherry@outlook.com
8
+ License: MIT
9
+ Keywords: Replidec
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
13
+ Classifier: Intended Audience :: Science/Research
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: biopython>=1.77
18
+ Requires-Dist: future>=0.18.2
19
+
20
+ ![Replidec Banner](replidec_banner.jpg)
21
+ # RepliDec: Replication Cycle Decipher for Phages
22
+
23
+ [![PyPI](https://img.shields.io/pypi/v/Replidec.svg)](https://pypi.python.org/pypi/Replidec)
24
+ [![Anaconda-Server Badge](https://anaconda.org/bioconda/replidec/badges/version.svg)](https://anaconda.org/bioconda/replidec)
25
+ [![Anaconda-Server Badge](https://anaconda.org/bioconda/replidec/badges/downloads.svg)](https://anaconda.org/bioconda/replidec)
26
+
27
+ ## Aim
28
+
29
+ RepliDec employs a Bayesian classifier combined with homology searching to accurately predict the replication cycle of prokaryotic viruses (Phages).
30
+
31
+ ## Install
32
+
33
+ ### Method 1: using Conda (Recommend using bioconda with the latest version)
34
+
35
+ ```bash
36
+ conda create -n replidec
37
+ conda activate replidec
38
+ conda install -c conda-forge -c bioconda replidec
39
+ or
40
+ conda install -c denglab -c conda-forge -c bioconda replidec
41
+ ```
42
+
43
+ ### Method 2: using Docker
44
+
45
+ ```bash
46
+ docker pull quay.io/biocontainers/replidec:0.3.5--pyhdfd78af_0
47
+ docker run quay.io/biocontainers/replidec:0.3.5--pyhdfd78af_0 Replidec -h
48
+ ## Example
49
+ docker run -v /your/host/data:/data/ quay.io/biocontainers/replidec:0.3.5--pyhdfd78af_0 Replidec -i data/your_inputfile -p
50
+ choose_mode_based_on_your_input_type -w data
51
+ ```
52
+
53
+ ### Method 3: using pip
54
+
55
+ If you install using pip, please make sure that `mmseqs`, `hmmsearch`, and `blastp` are set to $PATH, these software can be equal to or higher than the version list below
56
+
57
+ - MMseqs2 Version: 13.45111
58
+
59
+ - HMMER 3.3.2 (Nov 2020)
60
+
61
+ - Protein-Protein BLAST 2.5.0+
62
+
63
+ ```bash
64
+ pip3 install Replidec
65
+ ```
66
+
67
+ ## Usage: Overview
68
+
69
+ ```
70
+ Replidec [-p PROGRAM] [-i INPUT] [-w WORK_DIR] [-h HELP] [options]
71
+
72
+ options:
73
+ -h, --help show this help message and exit
74
+ -v, --version show program's version number and exit
75
+ -p , --program { multi_fasta | genome_table | protein_table }
76
+
77
+ multi_fasta mode:
78
+ input is a fasta file and treat each sequence as one virus
79
+
80
+ genome_table mode:
81
+ input is a tab separated file with two columns
82
+ ___1st column: sample name
83
+ ___2nd column: path to the genome sequence file of the virus
84
+
85
+ protein_table mode:
86
+ input is a tab separated file with two columns
87
+ ___1st column: sample name
88
+ ___2nd column: path to the protein file of the virus
89
+
90
+ -i , --input_file The input file, which can be a sequence file or an index table
91
+ -w , --work_dir Directory to store intermediate and final results (default = ./Replidec_results)
92
+ -n , --file_name Name of final summary file (default = prediction_summary.tsv)
93
+ -t , --threads Number of parallel threads (default = 4)
94
+ -e , --hmmer_Eval E-value threshold to filter hmmer result (default = 1e-5)
95
+ -E , --hmmer_parameters
96
+ Parameters used for hmmer (default = --noali --cpu 3)
97
+ -m , --mmseq_Eval E-value threshold to filter mmseqs2 result (default = 1e-5)
98
+ -M , --mmseq_parameters
99
+ Parameter used for mmseqs
100
+ (default = -s 7 --max-seqs 1 --alignment-mode 3 --alignment-output-mode 0 --min-aln-len 40 --cov-mode 0 --greedy-best-hits 1 --threads 3)
101
+ -b , --blastp_Eval E-value threshold to filter blast result (default =1e-5)
102
+ -B , --blastp_parameter
103
+ Parameters used for blastp (default = -num_threads 3)
104
+ -d, --db_redownload Remove and re-download database
105
+ ```
106
+
107
+ ## Usage: Download database (-d)
108
+ The database used in Replidec will be downloaded automatically.
109
+
110
+ Location: will be downloaded at the location where Replidec is installed
111
+
112
+ If you want to redownload the database, the `-d` parameter can be used. The older database will be moved to "discarded_db" in the workdir(-w); This dir can be removed manually by the user.
113
+
114
+
115
+ ## Usage: Input (-i) and program (-p)
116
+
117
+ ##**IMPORTANT**##
118
+ <br>
119
+ RepliDec assumes that all input contigs are either complete or partial phage genomes. Please ensure you pass your contigs through a viral discovery tool to exclude non-phage sequences to ensure accurate results.
120
+
121
+ **The input file is different based on different programs**
122
+
123
+ Replidec offers **3** different programs:
124
+
125
+ 1. 'multi_fasta'
126
+ 2. 'genome_table'
127
+ 3. 'protein_table',
128
+
129
+ ### multi_fasta mode:
130
+ * input is a **fasta** file and treat each sequence as one virus.
131
+ * Example: <your_path>/viral_contigs.fasta
132
+
133
+ ```
134
+ >contig_1
135
+ TATCGATCGATCGATCGATCGATCGTACGTACGTACGTACG...
136
+ >contig_2
137
+ CATCGATCGATCGATCGATCGATCGATCGATCGATCGATCG...
138
+ ...
139
+ ```
140
+
141
+ ### genome_table mode:
142
+ * input is a **tab** separated file with two columns.
143
+
144
+ * 1st column: sample name
145
+ * 2nd column: path to the genome sequence file of the virus
146
+ * Example: <your_path>/example_genomes.tsv
147
+
148
+ ```
149
+ contig_1 your/file/path/contig_1.fasta
150
+ contig_2 your/file/path/contig_2.fasta
151
+ contig_3 your/file/path/contig_3.fasta
152
+ ...
153
+ ```
154
+
155
+ ### protein_table mode:
156
+ * input is a **tab** separated file with two columns
157
+
158
+ * 1st column: sample name
159
+ * 2nd column: path to the protein file of the virus
160
+ * Example: <your_path>/example_proteins.tsv
161
+
162
+ ```
163
+ contig_1_prot your/file/path/contig_1.fasta
164
+ contig_2_prot your/file/path/contig_2.fasta
165
+ contig_3_prot your/file/path/contig_3.fasta
166
+ ...
167
+ ```
168
+
169
+ ## Usage: Output (-w and -n)
170
+ The output directory can be assigned with `-w , --work_dir `, where the intermediate files and the final prediction results will be stored. The name of the final summary file can be assigned with the `-n , --file_name` argument.
171
+
172
+ At the end of the analysis, the output directory would contain the following:
173
+ * BC_Inno: This directory contains the result file for dectect Innovirues
174
+ * BC_mmseqs: This directory contains the result file for mapping result to our custom database
175
+ * BC_pfam: This directory contains the result file for dectect the Integrase and Excisionase
176
+ * BC_prodigal: This directory contains the result file for CDS prediction from genome or contig sequence. (If {-p protein_table} is used, this directory will not be created.)
177
+ * prediction_summary.tsv: This file is the summary file of the prediction result. It contains multiple columns.
178
+ * sample_name: identifier. Can be a sequence ID or the first column of the plain text input file.
179
+
180
+ * integrase_number: the number of genes mapped to integrase meet the creteria(set by -c).
181
+
182
+ * excisionase_number: the number of genes mapped to excisionase meet the creteria(set by -c).
183
+
184
+ * pfam_label: if it contains integrase or excisionase, the label will be "Temperate". Otherwise "Virulent".
185
+
186
+ * bc_temperate: conditional probability of temperate|genes.
187
+
188
+ * bc_virulent: conditional probability of virulent|genes.
189
+
190
+ * bc_label: if bc_temperate greater than bc_virulent, label will be "Temperate". Otherwise "Virulent".
191
+
192
+ * final_label: if pfam_label and bc_label both is Temperate, then label will be "Temperate"; if an Innovirues marker gene exists, then label will be "Chronic"; otherwise "Virulent".
193
+
194
+ * match_gene_number: the number of genes mapped to our custom database.
195
+
196
+
197
+ ## Example (Data in test folder, please navigate to test folder first)
198
+ ```
199
+ cd test
200
+
201
+ ## Conda
202
+ ## test passed - genome_table
203
+ replidec -p genome_table -i example/genome_test.small.index -w opt_folder_genome_table
204
+
205
+ ## test passed - multi_fasta
206
+ replidec -p multi_fasta -i example/test.contig.small.fa -w opt_folder_multi_fasta
207
+
208
+ ## test passed - protein_table
209
+ replidec -p protein_table -i example/example.small.list -w opt_folder_protein_table
210
+
211
+
212
+ ## Docker
213
+ docker run -v /Your_path_clone_replidec/Replidec/test:/data/ quay.io/biocontainers/replidec:0.3.5--pyhdfd78af_0 Replidec -p multi_fasta -i /data/example/test.contig.small.new.fa -w /data/opt_folder_docker_multi_fasta
214
+ ```
215
+
216
+
217
+ ## Issues
218
+ ### Database can not be downloaded automatically
219
+ If the dataset cannot be automatically downloaded from Zenodo due to regional access restrictions, you may manually add it instead. The same database has also been uploaded to OSF as an alternative source.
220
+
221
+ 1. **Locate your Replidec installation path**
222
+ After installing Replidec via Conda or Docker, locate the installed directory. Typically, it can be found at:
223
+ `your_conda_path/envs/env_name/lib/python*/site-packages/Replidec`
224
+
225
+
226
+ 2. **Navigate to the Replidec folder**
227
+ Use the terminal to move into the directory:
228
+ `cd your_conda_path/envs/env_name/lib/python*/site-packages/Replidec`
229
+
230
+ 3. **Download the database manually from OSF (Project name: Replidec)**
231
+ Access the alternative download link here:
232
+ 👉 https://osf.io/thpkb/files/osfstorage
233
+
234
+ 4. **Extract the database**
235
+ After downloading, extract the contents of the archive into the Replidec directory, and a folder named "db" will be created:
236
+ `tar -zxvf db_v0.3.2.tar.gz`
237
+ ✅ Note: Make sure the extracted folder can be found in this path `your_conda_path/envs/env_name/lib/python*/site-packages/Replidec/db`.
238
+
239
+ For now, everything is fixed. Enjoy playing with Replidec!
240
+
241
+
242
+
@@ -0,0 +1,12 @@
1
+ Replidec/Replidec.py,sha256=Prc8wqVSPQ6ohnMpGfZhU_41KAJAU9oPwNrLIc0whu8,13576
2
+ Replidec/Replidec_cmdline.py,sha256=j6pTc6zX5k5WC3gSSdGlecqrzx10TAaqNDuBp_IKgMw,6699
3
+ Replidec/Replidec_multi.py,sha256=wFKkAOzWLtp_PNxdMi1x1tjhYMJ5cpfImtJMo_35iEI,12910
4
+ Replidec/__init__.py,sha256=RcETINSpABsnvIJyPVWVtM2AX8Pv0da1eMkQop5FFqg,487
5
+ Replidec/utility.py,sha256=NAsMUkTMOH3fxxhkPuojqFDax5Myz5bblMuqeZU0Ldw,1213
6
+ replidec-0.3.6.dist-info/LICENSE,sha256=0ICJkzBSEER28iOUCdn68F8LcBfoDToZ1eXxSHCv0bI,1067
7
+ replidec-0.3.6.dist-info/METADATA,sha256=39W524t0z51dA8Avv-mkscwsdqMviB4oIsI_ByJGYjs,9780
8
+ replidec-0.3.6.dist-info/WHEEL,sha256=BNRMDyzLkkcmlv0J8ppDQkk2VED33SesJDynr9ED1gc,91
9
+ replidec-0.3.6.dist-info/entry_points.txt,sha256=rz41hPhcFj9RWiEmUTgbYur-jiGViF6Ub4LXQTc2D-E,102
10
+ replidec-0.3.6.dist-info/top_level.txt,sha256=P8TNQZNPdiptZP_WEt4bffLZRuwZdOgsOUD3XEsGQYg,9
11
+ replidec-0.3.6.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
12
+ replidec-0.3.6.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.3.4)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ Replidec = Replidec.Replidec_cmdline:main
3
+ replidec = Replidec.Replidec_cmdline:main
@@ -0,0 +1 @@
1
+ Replidec
@@ -0,0 +1 @@
1
+