PGAP2 2.0__tar.gz → 2.3__tar.gz
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.
- {pgap2-2.0 → pgap2-2.3/PGAP2.egg-info}/PKG-INFO +3 -1
- {pgap2-2.0 → pgap2-2.3}/PGAP2.egg-info/SOURCES.txt +23 -1
- {pgap2-2.0 → pgap2-2.3}/PGAP2.egg-info/requires.txt +2 -0
- {pgap2-2.0/PGAP2.egg-info → pgap2-2.3}/PKG-INFO +3 -1
- pgap2-2.3/pgap2/cli/neighborhood.py +209 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/cli/postprocess.py +2 -0
- pgap2-2.3/pgap2/cli/sqlite_cmd.py +84 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/lib/pangenome.py +13 -13
- {pgap2-2.0 → pgap2-2.3}/pgap2/lib/phylogeny.py +71 -19
- pgap2-2.3/pgap2/lib/synteny.py +1055 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/main.py +4 -0
- pgap2-2.3/pgap2/models/__init__.py +30 -0
- pgap2-2.3/pgap2/models/baps.py +22 -0
- pgap2-2.3/pgap2/models/base.py +8 -0
- pgap2-2.3/pgap2/models/gene_annotation.py +40 -0
- pgap2-2.3/pgap2/models/gene_cluster.py +48 -0
- pgap2-2.3/pgap2/models/gene_neighborhood.py +79 -0
- pgap2-2.3/pgap2/models/pav.py +31 -0
- pgap2-2.3/pgap2/models/phylogeny.py +26 -0
- pgap2-2.3/pgap2/models/preprocess.py +53 -0
- pgap2-2.3/pgap2/models/project.py +53 -0
- pgap2-2.3/pgap2/models/project_file.py +54 -0
- pgap2-2.3/pgap2/models/statistics.py +83 -0
- pgap2-2.3/pgap2/models/strain.py +60 -0
- pgap2-2.3/pgap2/models/tajimas_d.py +25 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/postprocess/baps.py +6 -3
- {pgap2-2.0 → pgap2-2.3}/pgap2/postprocess/graph.py +3 -4
- pgap2-2.3/pgap2/postprocess/neighborhood.py +184 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/postprocess/profile.py +2 -1
- {pgap2-2.0 → pgap2-2.3}/pgap2/postprocess/singletree.py +7 -4
- {pgap2-2.0 → pgap2-2.3}/pgap2/postprocess/stat.py +103 -21
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/data_loader.py +1 -1
- pgap2-2.3/pgap2/utils/neighborhood.py +140 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/partition_add.py +1 -1
- pgap2-2.3/pgap2/utils/sqlite_builder.py +1211 -0
- {pgap2-2.0 → pgap2-2.3}/pyproject.toml +3 -1
- pgap2-2.3/tests/test_pangenome.py +19 -0
- pgap2-2.3/tests/test_post.py +95 -0
- pgap2-2.3/tests/test_synteny.py +861 -0
- pgap2-2.0/tests/test_post.py +0 -43
- {pgap2-2.0 → pgap2-2.3}/LICENSE +0 -0
- {pgap2-2.0 → pgap2-2.3}/MANIFEST.in +0 -0
- {pgap2-2.0 → pgap2-2.3}/PGAP2.egg-info/dependency_links.txt +0 -0
- {pgap2-2.0 → pgap2-2.3}/PGAP2.egg-info/entry_points.txt +0 -0
- {pgap2-2.0 → pgap2-2.3}/PGAP2.egg-info/top_level.txt +0 -0
- {pgap2-2.0 → pgap2-2.3}/README.md +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/__init__.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/cli/__init__.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/cli/partition.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/cli/partition_add.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/cli/partition_merge.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/cli/partition_minus.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/cli/preprocess.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/dependencies/draw_post_profile.r +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/dependencies/draw_post_stat.r +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/dependencies/draw_prep.r +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/dependencies/maskrc-svg.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/dependencies/run_fastbaps +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/lib/__init__.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/lib/basic.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/lib/panclust.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/lib/pangp.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/lib/pklcheck.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/lib/species.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/lib/strain.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/lib/tree.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/postprocess/__init__.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/postprocess/reference_layout.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/postprocess/tajimas_d.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/toy_input/287_91.fa +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/toy_input/287_91.gff +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/toy_input/AKU_12601.gff +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/toy_input/Readme_input.md +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/toy_input/SC_B67.gbk +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/__init__.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/alignment.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/arrangement_detector.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/draw.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/gene_retriever.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/generate_tree.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/graph_utils.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/partition.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/partition_merge.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/partition_minus.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/postprocess.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/preprocess.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/supply.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/pgap2/utils/tools.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/setup.cfg +0 -0
- {pgap2-2.0 → pgap2-2.3}/tests/test_main.py +0 -0
- {pgap2-2.0 → pgap2-2.3}/tests/test_prep.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PGAP2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3
|
|
4
4
|
Summary: PGAP2: a comprehensive pan-genome analysis pipeline for prokaryotic genomes
|
|
5
5
|
Author-email: bucongfan <bucf@big.ac.cn>
|
|
6
6
|
Maintainer-email: bucongfan <bucf@big.ac.cn>
|
|
@@ -28,6 +28,8 @@ Requires-Dist: ete3==3.1.3
|
|
|
28
28
|
Requires-Dist: svgwrite==1.4.3
|
|
29
29
|
Requires-Dist: miniFasta==3.0.2
|
|
30
30
|
Requires-Dist: tajimas_d==2.0.2
|
|
31
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
32
|
+
Requires-Dist: igraph
|
|
31
33
|
Dynamic: license-file
|
|
32
34
|
|
|
33
35
|

|
|
@@ -11,12 +11,14 @@ PGAP2.egg-info/top_level.txt
|
|
|
11
11
|
pgap2/__init__.py
|
|
12
12
|
pgap2/main.py
|
|
13
13
|
pgap2/cli/__init__.py
|
|
14
|
+
pgap2/cli/neighborhood.py
|
|
14
15
|
pgap2/cli/partition.py
|
|
15
16
|
pgap2/cli/partition_add.py
|
|
16
17
|
pgap2/cli/partition_merge.py
|
|
17
18
|
pgap2/cli/partition_minus.py
|
|
18
19
|
pgap2/cli/postprocess.py
|
|
19
20
|
pgap2/cli/preprocess.py
|
|
21
|
+
pgap2/cli/sqlite_cmd.py
|
|
20
22
|
pgap2/dependencies/draw_post_profile.r
|
|
21
23
|
pgap2/dependencies/draw_post_stat.r
|
|
22
24
|
pgap2/dependencies/draw_prep.r
|
|
@@ -31,10 +33,26 @@ pgap2/lib/phylogeny.py
|
|
|
31
33
|
pgap2/lib/pklcheck.py
|
|
32
34
|
pgap2/lib/species.py
|
|
33
35
|
pgap2/lib/strain.py
|
|
36
|
+
pgap2/lib/synteny.py
|
|
34
37
|
pgap2/lib/tree.py
|
|
38
|
+
pgap2/models/__init__.py
|
|
39
|
+
pgap2/models/baps.py
|
|
40
|
+
pgap2/models/base.py
|
|
41
|
+
pgap2/models/gene_annotation.py
|
|
42
|
+
pgap2/models/gene_cluster.py
|
|
43
|
+
pgap2/models/gene_neighborhood.py
|
|
44
|
+
pgap2/models/pav.py
|
|
45
|
+
pgap2/models/phylogeny.py
|
|
46
|
+
pgap2/models/preprocess.py
|
|
47
|
+
pgap2/models/project.py
|
|
48
|
+
pgap2/models/project_file.py
|
|
49
|
+
pgap2/models/statistics.py
|
|
50
|
+
pgap2/models/strain.py
|
|
51
|
+
pgap2/models/tajimas_d.py
|
|
35
52
|
pgap2/postprocess/__init__.py
|
|
36
53
|
pgap2/postprocess/baps.py
|
|
37
54
|
pgap2/postprocess/graph.py
|
|
55
|
+
pgap2/postprocess/neighborhood.py
|
|
38
56
|
pgap2/postprocess/profile.py
|
|
39
57
|
pgap2/postprocess/reference_layout.py
|
|
40
58
|
pgap2/postprocess/singletree.py
|
|
@@ -53,14 +71,18 @@ pgap2/utils/draw.py
|
|
|
53
71
|
pgap2/utils/gene_retriever.py
|
|
54
72
|
pgap2/utils/generate_tree.py
|
|
55
73
|
pgap2/utils/graph_utils.py
|
|
74
|
+
pgap2/utils/neighborhood.py
|
|
56
75
|
pgap2/utils/partition.py
|
|
57
76
|
pgap2/utils/partition_add.py
|
|
58
77
|
pgap2/utils/partition_merge.py
|
|
59
78
|
pgap2/utils/partition_minus.py
|
|
60
79
|
pgap2/utils/postprocess.py
|
|
61
80
|
pgap2/utils/preprocess.py
|
|
81
|
+
pgap2/utils/sqlite_builder.py
|
|
62
82
|
pgap2/utils/supply.py
|
|
63
83
|
pgap2/utils/tools.py
|
|
64
84
|
tests/test_main.py
|
|
85
|
+
tests/test_pangenome.py
|
|
65
86
|
tests/test_post.py
|
|
66
|
-
tests/test_prep.py
|
|
87
|
+
tests/test_prep.py
|
|
88
|
+
tests/test_synteny.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PGAP2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3
|
|
4
4
|
Summary: PGAP2: a comprehensive pan-genome analysis pipeline for prokaryotic genomes
|
|
5
5
|
Author-email: bucongfan <bucf@big.ac.cn>
|
|
6
6
|
Maintainer-email: bucongfan <bucf@big.ac.cn>
|
|
@@ -28,6 +28,8 @@ Requires-Dist: ete3==3.1.3
|
|
|
28
28
|
Requires-Dist: svgwrite==1.4.3
|
|
29
29
|
Requires-Dist: miniFasta==3.0.2
|
|
30
30
|
Requires-Dist: tajimas_d==2.0.2
|
|
31
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
32
|
+
Requires-Dist: igraph
|
|
31
33
|
Dynamic: license-file
|
|
32
34
|
|
|
33
35
|

|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI definitions for the 'neigh' (conserved gene neighborhood) subcommand.
|
|
3
|
+
|
|
4
|
+
This subcommand runs the full partition pipeline (identical to 'main')
|
|
5
|
+
followed by a conserved gene neighborhood discovery step inspired by
|
|
6
|
+
spacedust's agglomerative clustering algorithm.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import argparse
|
|
11
|
+
|
|
12
|
+
from argparse import ArgumentParser, _SubParsersAction
|
|
13
|
+
|
|
14
|
+
from pgap2.utils.supply import sfw, tqdm_
|
|
15
|
+
from pgap2.utils.supply import set_verbosity_level
|
|
16
|
+
from pgap2.utils.graph_utils import check_min_falen, check_gcode
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def launch(args: argparse.Namespace):
|
|
20
|
+
from pgap2.utils.neighborhood import main
|
|
21
|
+
main(
|
|
22
|
+
indir=os.path.abspath(args.indir),
|
|
23
|
+
outdir=os.path.abspath(args.outdir),
|
|
24
|
+
falen=args.min_falen, threads=args.threads, evalue=args.evalue,
|
|
25
|
+
aligner=args.aligner, clust_method=args.clust_method,
|
|
26
|
+
orth_id=args.orth_id, para_id=args.para_id, dup_id=args.dup_id,
|
|
27
|
+
id_attr_key=args.id_attr_key, type_filter=args.type_filter,
|
|
28
|
+
coverage=0.98, fast_mode=args.fast_mode, hconf_thre=args.hconf_thre,
|
|
29
|
+
LD=args.LD, AS=args.AS, AL=args.AL, max_targets=args.max_targets,
|
|
30
|
+
context_similarity=0, flank=5,
|
|
31
|
+
accurate=args.accurate,
|
|
32
|
+
exhaust_orth=args.exhaust_orth,
|
|
33
|
+
gcode=args.gcode,
|
|
34
|
+
disable=args.disable, annot=args.annot, retrieve=args.retrieve,
|
|
35
|
+
radius=args.radius, sensitivity=args.sensitivity, ins=args.ins,
|
|
36
|
+
# neighborhood-specific parameters
|
|
37
|
+
max_gap=args.max_gap,
|
|
38
|
+
min_genes=args.min_genes,
|
|
39
|
+
pval_threshold=args.pval,
|
|
40
|
+
min_genomes=args.min_genomes,
|
|
41
|
+
resolution=args.resolution,
|
|
42
|
+
use_order=args.use_order,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def neighborhood_portal(args):
|
|
47
|
+
set_verbosity_level(args.outdir, args.verbose,
|
|
48
|
+
args.debug, 'neighborhood')
|
|
49
|
+
|
|
50
|
+
if args.clust_method == 'mmseqs2':
|
|
51
|
+
sfw.check_dependency("mmseqs2")
|
|
52
|
+
elif args.clust_method == 'cdhit':
|
|
53
|
+
sfw.check_dependency("cdhit")
|
|
54
|
+
|
|
55
|
+
sfw.check_dependency("mcl")
|
|
56
|
+
sfw.check_dependency("mcxdeblast")
|
|
57
|
+
if args.aligner == 'diamond':
|
|
58
|
+
sfw.check_dependency("diamond")
|
|
59
|
+
elif args.aligner == 'blastp':
|
|
60
|
+
sfw.check_dependency("blastp")
|
|
61
|
+
sfw.check_dependency("makeblastdb")
|
|
62
|
+
|
|
63
|
+
if args.retrieve:
|
|
64
|
+
sfw.check_dependency("miniprot")
|
|
65
|
+
sfw.check_dependency("seqtk")
|
|
66
|
+
tqdm_.set_total_step(14) # 12 partition + 2 neighborhood steps
|
|
67
|
+
else:
|
|
68
|
+
tqdm_.set_total_step(9) # 7 partition + 2 neighborhood steps
|
|
69
|
+
|
|
70
|
+
if args.annot:
|
|
71
|
+
sfw.check_dependency("prodigal")
|
|
72
|
+
|
|
73
|
+
launch(args)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def neighborhood_cmd(subparser: _SubParsersAction):
|
|
77
|
+
|
|
78
|
+
subparser_neigh: ArgumentParser = subparser.add_parser(
|
|
79
|
+
'neigh',
|
|
80
|
+
help='Conserved gene neighborhood (synteny block) analysis. '
|
|
81
|
+
'Runs the full partition pipeline then discovers conserved '
|
|
82
|
+
'gene neighborhoods across genomes (spacedust-style).',
|
|
83
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
84
|
+
|
|
85
|
+
# ---- Partition parameters (same as 'main') ----
|
|
86
|
+
subparser_neigh.add_argument(
|
|
87
|
+
'--indir', '-i', required=True,
|
|
88
|
+
help='Input directory containing genome files. Same prefix = same strain.')
|
|
89
|
+
subparser_neigh.add_argument(
|
|
90
|
+
'--outdir', '-o', required=False, default='.',
|
|
91
|
+
help='Output directory')
|
|
92
|
+
subparser_neigh.add_argument(
|
|
93
|
+
'--dup_id', required=False, type=float, default=0.99,
|
|
94
|
+
help='Maximum identity for most recent duplication events.')
|
|
95
|
+
subparser_neigh.add_argument(
|
|
96
|
+
'--orth_id', required=False, type=float, default=0.98,
|
|
97
|
+
help='Maximum identity between most similar pan-clusters.')
|
|
98
|
+
subparser_neigh.add_argument(
|
|
99
|
+
'--para_id', required=False, type=float, default=0.7,
|
|
100
|
+
help='Paralogous identity threshold.')
|
|
101
|
+
subparser_neigh.add_argument(
|
|
102
|
+
'--type-filter', required=False, type=str, default='CDS',
|
|
103
|
+
help='GFF feature type (3rd column) to include.')
|
|
104
|
+
subparser_neigh.add_argument(
|
|
105
|
+
'--id-attr-key', required=False, type=str, default='ID',
|
|
106
|
+
help='GFF attribute key for record ID.')
|
|
107
|
+
subparser_neigh.add_argument(
|
|
108
|
+
'--hconf_thre', required=False, type=float, default=1,
|
|
109
|
+
help='High confidence cluster threshold.')
|
|
110
|
+
subparser_neigh.add_argument(
|
|
111
|
+
'--exhaust_orth', '-e', required=False, action='store_true',
|
|
112
|
+
default=False, help='Exhaustive paralog splitting.')
|
|
113
|
+
subparser_neigh.add_argument(
|
|
114
|
+
'--sensitivity', '-s', required=False, type=str, default='strict',
|
|
115
|
+
choices=('soft', 'moderate', 'strict'),
|
|
116
|
+
help='Node connectivity sensitivity.')
|
|
117
|
+
subparser_neigh.add_argument(
|
|
118
|
+
'--ins', '-n', required=False, action='store_true', default=False,
|
|
119
|
+
help='Ignore insertion sequence influence.')
|
|
120
|
+
subparser_neigh.add_argument(
|
|
121
|
+
'--fast', '-f', dest='fast_mode', required=False,
|
|
122
|
+
action='store_true', default=False,
|
|
123
|
+
help='Fast mode (skip fine feature analysis).')
|
|
124
|
+
subparser_neigh.add_argument(
|
|
125
|
+
'--accurate', '-a', required=False, action='store_true',
|
|
126
|
+
default=False, help='Bidirection check for paralog partition.')
|
|
127
|
+
subparser_neigh.add_argument(
|
|
128
|
+
'--retrieve', '-r', required=False, action='store_true',
|
|
129
|
+
default=False, help='Retrieve genes lost during annotation.')
|
|
130
|
+
subparser_neigh.add_argument(
|
|
131
|
+
'--threads', '-t', required=False, default=1, type=int,
|
|
132
|
+
help='Number of threads.')
|
|
133
|
+
subparser_neigh.add_argument(
|
|
134
|
+
'--max_targets', '-k', required=False, type=int, default=2000,
|
|
135
|
+
help='Maximum alignment targets per query.')
|
|
136
|
+
subparser_neigh.add_argument(
|
|
137
|
+
'--LD', required=False, type=float, default=0.6,
|
|
138
|
+
help='Minimum gene length difference proportion.')
|
|
139
|
+
subparser_neigh.add_argument(
|
|
140
|
+
'--AS', required=False, type=float, default=0.6,
|
|
141
|
+
help='Coverage for shorter sequence.')
|
|
142
|
+
subparser_neigh.add_argument(
|
|
143
|
+
'--AL', required=False, type=float, default=0.6,
|
|
144
|
+
help='Coverage for longer sequence.')
|
|
145
|
+
subparser_neigh.add_argument(
|
|
146
|
+
'--evalue', required=False, type=float, default=1E-5,
|
|
147
|
+
help='Aligner E-value threshold.')
|
|
148
|
+
subparser_neigh.add_argument(
|
|
149
|
+
'--aligner', required=False, type=str, default='diamond',
|
|
150
|
+
choices=('diamond', 'blastp'), help='Pairwise aligner.')
|
|
151
|
+
subparser_neigh.add_argument(
|
|
152
|
+
'--clust_method', required=False, type=str, default='mmseqs2',
|
|
153
|
+
choices=('cdhit', 'mmseqs2'), help='Gene clustering method.')
|
|
154
|
+
subparser_neigh.add_argument(
|
|
155
|
+
'--radius', required=False, type=int, default=3,
|
|
156
|
+
help='Search region radius.')
|
|
157
|
+
subparser_neigh.add_argument(
|
|
158
|
+
'--min_falen', '-m', required=False, type=check_min_falen,
|
|
159
|
+
default=20, help='Minimum protein length (at least 11).')
|
|
160
|
+
subparser_neigh.add_argument(
|
|
161
|
+
'--gcode', required=False, type=check_gcode, default=11,
|
|
162
|
+
help='Genetic code.')
|
|
163
|
+
subparser_neigh.add_argument(
|
|
164
|
+
'--annot', required=False, action='store_true', default=False,
|
|
165
|
+
help='Re-annotate genomes with Prodigal.')
|
|
166
|
+
|
|
167
|
+
# ---- Neighborhood-specific parameters ----
|
|
168
|
+
neigh_group = subparser_neigh.add_argument_group(
|
|
169
|
+
'Neighborhood parameters',
|
|
170
|
+
'Parameters specific to conserved gene neighborhood detection.')
|
|
171
|
+
neigh_group.add_argument(
|
|
172
|
+
'--max_gap', required=False, type=int, default=3,
|
|
173
|
+
help='Maximum gene gap allowed between hits for merging into a '
|
|
174
|
+
'syntenic block. Larger values allow more intervening genes.')
|
|
175
|
+
neigh_group.add_argument(
|
|
176
|
+
'-mg','--min_genes', required=False, type=int, default=2,
|
|
177
|
+
help='Minimum number of genes to define a syntenic block.')
|
|
178
|
+
neigh_group.add_argument(
|
|
179
|
+
'--pval', required=False, type=float, default=0.01,
|
|
180
|
+
help='P-value threshold for gene-order filtering (only used '
|
|
181
|
+
'when --use_order is enabled).')
|
|
182
|
+
neigh_group.add_argument(
|
|
183
|
+
'--use_order', required=False, action='store_true', default=False,
|
|
184
|
+
help='Enable gene-order conservation filtering. When set, blocks '
|
|
185
|
+
'are filtered by P_order <= --pval, edge weights use '
|
|
186
|
+
'-log10(P_order), and neighborhood P-values are computed '
|
|
187
|
+
'via Fisher\'s combined test. Default: off (accept blocks '
|
|
188
|
+
'purely based on --min_genes co-localization).')
|
|
189
|
+
neigh_group.add_argument(
|
|
190
|
+
'-mgs','--min_genomes', required=False, type=int, default=2,
|
|
191
|
+
help='Minimum number of genomes a neighborhood must span to be reported.')
|
|
192
|
+
neigh_group.add_argument(
|
|
193
|
+
'-re','--resolution', required=False, type=float, default=1.0,
|
|
194
|
+
help='Louvain resolution parameter. Higher values produce smaller, '
|
|
195
|
+
'more focused neighborhoods; lower values produce larger, '
|
|
196
|
+
'more inclusive ones. Try 2.0-10.0 if neighborhoods are too large.')
|
|
197
|
+
|
|
198
|
+
# ---- Common flags ----
|
|
199
|
+
subparser_neigh.add_argument(
|
|
200
|
+
'--verbose', '-v', required=False, action='store_true',
|
|
201
|
+
default=False, help='Verbose output')
|
|
202
|
+
subparser_neigh.add_argument(
|
|
203
|
+
'--debug', '-D', required=False, action='store_true',
|
|
204
|
+
default=False, help='Debug mode (very verbose)')
|
|
205
|
+
subparser_neigh.add_argument(
|
|
206
|
+
'--disable', required=False, action='store_true',
|
|
207
|
+
default=False, help='Disable progress bar')
|
|
208
|
+
|
|
209
|
+
subparser_neigh.set_defaults(func=neighborhood_portal)
|
|
@@ -7,6 +7,7 @@ from pgap2.postprocess.baps import baps_cmd
|
|
|
7
7
|
from pgap2.postprocess.singletree import singletree_cmd
|
|
8
8
|
from pgap2.postprocess.tajimas_d import tajimas_d_cmd
|
|
9
9
|
from pgap2.postprocess.graph import graph_cmd
|
|
10
|
+
from pgap2.postprocess.neighborhood import neigh_cmd
|
|
10
11
|
from argparse import ArgumentParser, _SubParsersAction
|
|
11
12
|
import argparse
|
|
12
13
|
|
|
@@ -21,3 +22,4 @@ def postprocess_cmd(subparser: _SubParsersAction):
|
|
|
21
22
|
baps_cmd(sub_subparsers)
|
|
22
23
|
tajimas_d_cmd(sub_subparsers)
|
|
23
24
|
graph_cmd(sub_subparsers)
|
|
25
|
+
neigh_cmd(sub_subparsers)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""CLI definitions for the ``pgap2 sqlite`` subcommand.
|
|
2
|
+
|
|
3
|
+
Generates a self-contained SQLite database from PGAP2 output files.
|
|
4
|
+
|
|
5
|
+
Usage::
|
|
6
|
+
|
|
7
|
+
pgap2 sqlite -i /path/to/output
|
|
8
|
+
pgap2 sqlite -i /path/to/output -o /path/to/output/pgap2.db
|
|
9
|
+
pgap2 sqlite -i /path/to/output --name "My Project" --species "E. coli"
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import argparse
|
|
14
|
+
from argparse import ArgumentParser, _SubParsersAction
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def launch(args: argparse.Namespace):
|
|
19
|
+
from pgap2.utils.sqlite_builder import build_sqlite, parse_readme
|
|
20
|
+
|
|
21
|
+
indir = os.path.abspath(args.indir)
|
|
22
|
+
|
|
23
|
+
# Resolve output path: default is <indir>/pgap2.db
|
|
24
|
+
if args.output:
|
|
25
|
+
output_path = os.path.abspath(args.output)
|
|
26
|
+
else:
|
|
27
|
+
output_path = os.path.join(indir, "pgap2.db")
|
|
28
|
+
|
|
29
|
+
# Try to derive project name / species from README.md if not provided
|
|
30
|
+
project_name = args.name
|
|
31
|
+
species = args.species
|
|
32
|
+
|
|
33
|
+
readme_path = Path(indir) / "README.md"
|
|
34
|
+
if readme_path.exists():
|
|
35
|
+
readme_info = parse_readme(readme_path)
|
|
36
|
+
if not project_name:
|
|
37
|
+
project_name = readme_info.get("project_name", "")
|
|
38
|
+
if not species:
|
|
39
|
+
species = readme_info.get("species", "")
|
|
40
|
+
|
|
41
|
+
if not project_name:
|
|
42
|
+
project_name = Path(indir).name
|
|
43
|
+
if not species:
|
|
44
|
+
species = "Unknown"
|
|
45
|
+
|
|
46
|
+
info = build_sqlite(
|
|
47
|
+
data_dir=indir,
|
|
48
|
+
output_path=output_path,
|
|
49
|
+
project_name=project_name,
|
|
50
|
+
species=species,
|
|
51
|
+
description=args.description or "",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def sqlite_portal(args):
|
|
58
|
+
"""Entry point called by argparse (dependency checks + launch)."""
|
|
59
|
+
launch(args)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def sqlite_cmd(subparser: _SubParsersAction):
|
|
63
|
+
"""Register the ``sqlite`` subcommand."""
|
|
64
|
+
sp: ArgumentParser = subparser.add_parser(
|
|
65
|
+
"sqlite",
|
|
66
|
+
help="Generate a SQLite database from PGAP2 output files",
|
|
67
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
68
|
+
)
|
|
69
|
+
sp.add_argument(
|
|
70
|
+
"--indir", "-i", required=True,
|
|
71
|
+
help="PGAP2 output directory (contains .pav, .detail.tsv, etc.)")
|
|
72
|
+
sp.add_argument(
|
|
73
|
+
"--output", "-o", required=False, default=None,
|
|
74
|
+
help="Output SQLite file path (default: <indir>/pgap2.db)")
|
|
75
|
+
sp.add_argument(
|
|
76
|
+
"--name", "-n", required=False, default=None,
|
|
77
|
+
help="Project name (auto-detected from README.md if present)")
|
|
78
|
+
sp.add_argument(
|
|
79
|
+
"--species", "-s", required=False, default=None,
|
|
80
|
+
help="Species name (auto-detected from README.md if present)")
|
|
81
|
+
sp.add_argument(
|
|
82
|
+
"--description", "-d", required=False, default="",
|
|
83
|
+
help="Optional project description")
|
|
84
|
+
sp.set_defaults(func=sqlite_portal)
|
|
@@ -272,8 +272,7 @@ class Pangenome():
|
|
|
272
272
|
bar = tqdm(total=self.total_gene_num,
|
|
273
273
|
unit=' Gene', desc=tqdm_.step(6), disable=self.disable_tqdm)
|
|
274
274
|
loaded_contig_name = set()
|
|
275
|
-
|
|
276
|
-
coord_pat = re.compile(r'\[(\d+):\d+\]\(([+-])\)')
|
|
275
|
+
coord_pat = re.compile(r'\[(\d+):\d+\]\(([+-])\)')
|
|
277
276
|
|
|
278
277
|
with open(flat_annot_file) as fh:
|
|
279
278
|
for line in fh:
|
|
@@ -283,28 +282,29 @@ class Pangenome():
|
|
|
283
282
|
bar.update()
|
|
284
283
|
gene_index = lines[0]
|
|
285
284
|
contig_name = lines[2]
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
285
|
+
contig_key = ':'.join(gene_index.split(':')[:2])
|
|
286
|
+
if contig_key not in loaded_contig_name:
|
|
287
|
+
#may duplicate when use contig name as the global var, chaning to genome_id:contig_id
|
|
288
|
+
loaded_contig_name.add(contig_key)
|
|
289
|
+
self.annot_contig_map[contig_key] = contig_name
|
|
290
290
|
gene_len = int(lines[4])
|
|
291
291
|
gene_id = lines[5]
|
|
292
292
|
gene_name = lines[6]
|
|
293
293
|
gene_product = lines[7]
|
|
294
|
+
strand = ''
|
|
295
|
+
m = coord_pat.search(lines[3])
|
|
296
|
+
if m:
|
|
297
|
+
start = int(m.group(1))
|
|
298
|
+
strand = m.group(2) # '+' or '-'
|
|
294
299
|
if retrieve:
|
|
295
|
-
# start = int(re.search(r'\[(\d+):', lines[3]).group(1))
|
|
296
|
-
# strand = re.search(r'\[(\+|-)\d+:\d+\]', lines[3]).group(1)
|
|
297
|
-
m = coord_pat.search(lines[3])
|
|
298
|
-
if m:
|
|
299
|
-
start = int(m.group(1)) # 起始坐标
|
|
300
|
-
strand = m.group(2) # '+' 或 '-'
|
|
301
300
|
contig = ":".join(lines[0].split(":")[:2])
|
|
302
301
|
if strand == '+':
|
|
303
302
|
self.gene_rank[contig][0].append(start)
|
|
304
303
|
else:
|
|
305
304
|
self.gene_rank[contig][1].append(start)
|
|
306
305
|
self.annot.update({gene_index: {'len': gene_len, 'id': gene_id,
|
|
307
|
-
'name': gene_name, 'product': gene_product
|
|
306
|
+
'name': gene_name, 'product': gene_product,
|
|
307
|
+
'strand': strand}})
|
|
308
308
|
bar.close()
|
|
309
309
|
else:
|
|
310
310
|
logger.error(f'Cannot find the annot file {flat_annot_file}')
|
|
@@ -45,7 +45,7 @@ def set_logger(logger_):
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class Phylogeny():
|
|
48
|
-
def __init__(self, basic, outdir, threads, disable, msa_method, tree_method, fastbaps_levels=None, fastbaps_prior=None, add_paras=[]) -> None:
|
|
48
|
+
def __init__(self, basic, outdir, threads, disable, msa_method, tree_method, fastbaps_levels=None, fastbaps_prior=None, add_paras=[], no_partition=False) -> None:
|
|
49
49
|
self.basic: Basic = basic
|
|
50
50
|
self.outdir = outdir
|
|
51
51
|
self.threads = threads
|
|
@@ -54,6 +54,7 @@ class Phylogeny():
|
|
|
54
54
|
self.tree_method = tree_method
|
|
55
55
|
self.fastbaps_levels = fastbaps_levels
|
|
56
56
|
self.fastbaps_prior = fastbaps_prior
|
|
57
|
+
self.no_partition = no_partition
|
|
57
58
|
self.add_paras_dict = {2: '', 4: '',
|
|
58
59
|
6: '', 7: '', 8: '', 9: '', 10: ''}
|
|
59
60
|
self.load_additional_paras(add_paras)
|
|
@@ -359,6 +360,7 @@ class Phylogeny():
|
|
|
359
360
|
concatenated_gb.annotations['molecule_type'] = 'DNA'
|
|
360
361
|
|
|
361
362
|
position_offset = 0 # Track the concatenated sequence length
|
|
363
|
+
partition_entries = [] # RAxML-style partition definitions
|
|
362
364
|
|
|
363
365
|
real_involved_clusters = {
|
|
364
366
|
strain: 0 for strain in self.basic.strain_dict}
|
|
@@ -400,8 +402,23 @@ class Phylogeny():
|
|
|
400
402
|
type="region",
|
|
401
403
|
qualifiers={"note": core_cluster_path, "locus_tag": core_cluster})
|
|
402
404
|
concatenated_gb.features.append(feature)
|
|
405
|
+
# Record partition range (1-based) for RAxML/IQ-TREE partition model
|
|
406
|
+
partition_entries.append(
|
|
407
|
+
f'GTR+G, {core_cluster} = {position_offset + 1}-{position_offset + example_length}')
|
|
403
408
|
position_offset += example_length # Update position for the next cluster
|
|
404
409
|
|
|
410
|
+
# Save partition file (RAxML-style, also accepted by IQ-TREE with -p)
|
|
411
|
+
if not self.no_partition:
|
|
412
|
+
partition_path = os.path.join(
|
|
413
|
+
cat_align_outdir, 'core_gene_alignment.partitions')
|
|
414
|
+
with open(partition_path, 'w') as fh:
|
|
415
|
+
fh.write('\n'.join(partition_entries) + '\n')
|
|
416
|
+
logger.info(
|
|
417
|
+
f'Partition file: {partition_path} ({len(partition_entries)} partitions)')
|
|
418
|
+
else:
|
|
419
|
+
partition_path = None
|
|
420
|
+
logger.info('Partition file generation skipped (--no-partition)')
|
|
421
|
+
|
|
405
422
|
# Save the concatenated record to a GenBank file
|
|
406
423
|
SeqIO.write(concatenated_gb, os.path.join(
|
|
407
424
|
cat_align_outdir, 'core_gene_alignment.gb'), 'genbank')
|
|
@@ -424,6 +441,9 @@ class Phylogeny():
|
|
|
424
441
|
self.results_file.append(f'{cat_align_outdir}/core_gene_alignment.aln')
|
|
425
442
|
self.results_file.append(f'{cat_align_outdir}/core_gene_alignment.gb')
|
|
426
443
|
self.results_file.append(f'{cat_align_outdir}/core_gene_alignment.txt')
|
|
444
|
+
if partition_path is not None:
|
|
445
|
+
self.results_file.append(partition_path)
|
|
446
|
+
self.partition_file = partition_path
|
|
427
447
|
return f'{cat_align_outdir}/core_gene_alignment.aln'
|
|
428
448
|
|
|
429
449
|
def construct_tree(self, wd):
|
|
@@ -433,18 +453,29 @@ class Phylogeny():
|
|
|
433
453
|
os.makedirs(tree_outdir, exist_ok=True)
|
|
434
454
|
tree_path = os.path.join(tree_outdir, 'core_gene_alignment')
|
|
435
455
|
add_paras = self.add_paras_dict[6]
|
|
456
|
+
extra = (add_paras or "").strip()
|
|
457
|
+
model_arg = 'GTR+G'
|
|
458
|
+
|
|
436
459
|
if self.tree_method == 'raxml':
|
|
437
|
-
cmd =
|
|
438
|
-
|
|
439
|
-
|
|
460
|
+
cmd = (
|
|
461
|
+
f'{sfw.raxml} --msa {self.first_aln} --model {model_arg} '
|
|
462
|
+
f'--prefix {tree_path} --all --bs-trees 100 --redo {extra}'
|
|
463
|
+
).strip()
|
|
464
|
+
best_tree = f'{tree_path}.raxml.support'
|
|
465
|
+
|
|
440
466
|
elif self.tree_method == 'fasttree':
|
|
441
|
-
cmd =
|
|
442
|
-
|
|
443
|
-
|
|
467
|
+
cmd = (
|
|
468
|
+
f'{sfw.fasttree} -nt -gtr -gamma {extra} '
|
|
469
|
+
f'-log {tree_path}.log {self.first_aln} > {tree_path}.treefile'
|
|
470
|
+
).strip()
|
|
471
|
+
best_tree = f'{tree_path}.treefile'
|
|
472
|
+
|
|
444
473
|
elif self.tree_method == 'iqtree':
|
|
445
|
-
cmd =
|
|
446
|
-
|
|
447
|
-
|
|
474
|
+
cmd = (
|
|
475
|
+
f'{sfw.iqtree} -s {self.first_aln} -m {model_arg} '
|
|
476
|
+
f'-pre {tree_path} -bb 1000 -bnni -redo -nt AUTO {extra}'
|
|
477
|
+
).strip()
|
|
478
|
+
best_tree = f'{tree_path}.treefile'
|
|
448
479
|
|
|
449
480
|
self.results_file.append(best_tree)
|
|
450
481
|
|
|
@@ -529,19 +560,40 @@ class Phylogeny():
|
|
|
529
560
|
os.makedirs(tree_outdir, exist_ok=True)
|
|
530
561
|
tree_path = os.path.join(tree_outdir, 'reconstructed_tree')
|
|
531
562
|
add_paras = self.add_paras_dict[9]
|
|
563
|
+
extra = (add_paras or "").strip()
|
|
564
|
+
|
|
565
|
+
# Use partition model when partition file is available
|
|
566
|
+
has_partition = hasattr(
|
|
567
|
+
self, 'partition_file') and os.path.exists(self.partition_file)
|
|
532
568
|
|
|
533
569
|
if self.tree_method == 'raxml':
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
570
|
+
model_arg = self.partition_file if has_partition else 'GTR+G'
|
|
571
|
+
cmd = (
|
|
572
|
+
f'{sfw.raxml} --msa {self.maskrc_aln} --model {model_arg} '
|
|
573
|
+
f'--prefix {tree_path} --all --bs-trees 100 --redo {extra}'
|
|
574
|
+
).strip()
|
|
575
|
+
best_tree = f'{tree_path}.raxml.support' # 带 bootstrap 支持值的最终树
|
|
576
|
+
|
|
537
577
|
elif self.tree_method == 'fasttree':
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
578
|
+
# FastTree does not support partition models
|
|
579
|
+
cmd = (
|
|
580
|
+
f'{sfw.fasttree} -nt -gtr -gamma {extra} '
|
|
581
|
+
f'-log {tree_path}.log {self.maskrc_aln} > {tree_path}.treefile'
|
|
582
|
+
).strip()
|
|
583
|
+
best_tree = f'{tree_path}.treefile'
|
|
584
|
+
|
|
541
585
|
elif self.tree_method == 'iqtree':
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
586
|
+
if has_partition:
|
|
587
|
+
cmd = (
|
|
588
|
+
f'{sfw.iqtree} -s {self.maskrc_aln} -p {self.partition_file} '
|
|
589
|
+
f'-pre {tree_path} -m MFP+MERGE -bb 1000 -bnni -redo -nt AUTO {extra}'
|
|
590
|
+
).strip()
|
|
591
|
+
else:
|
|
592
|
+
cmd = (
|
|
593
|
+
f'{sfw.iqtree} -s {self.maskrc_aln} -m GTR+G '
|
|
594
|
+
f'-pre {tree_path} -bb 1000 -bnni -redo -nt AUTO {extra}'
|
|
595
|
+
).strip()
|
|
596
|
+
best_tree = f'{tree_path}.treefile'
|
|
545
597
|
|
|
546
598
|
self.results_file.append(best_tree)
|
|
547
599
|
finished_status = self.check_before_run(cmd, tree_outdir)
|