metabintools 0.2.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.
- metabintools/__init__.py +0 -0
- metabintools/bin_utils.py +26 -0
- metabintools/binstatistics.py +68 -0
- metabintools/cli/__init__.py +0 -0
- metabintools/cli/cli.py +40 -0
- metabintools/cli/commands/export/__init__.py +17 -0
- metabintools/cli/commands/export/contig2bin.py +61 -0
- metabintools/cli/commands/export/fasta.py +88 -0
- metabintools/cli/commands/export/gff.py +71 -0
- metabintools/cli/commands/import_data/__init__.py +25 -0
- metabintools/cli/commands/import_data/import_annotation.py +65 -0
- metabintools/cli/commands/import_data/import_asm.py +65 -0
- metabintools/cli/commands/import_data/import_bins.py +92 -0
- metabintools/cli/commands/import_data/import_coverage.py +66 -0
- metabintools/cli/commands/import_data/import_quality.py +72 -0
- metabintools/cli/commands/import_data/import_taxonomy.py +68 -0
- metabintools/cli/commands/merge.py +61 -0
- metabintools/cli/commands/rename.py +79 -0
- metabintools/cli/commands/summarise/__init__.py +17 -0
- metabintools/cli/commands/summarise/bins.py +68 -0
- metabintools/cli/commands/summarise/contigs.py +48 -0
- metabintools/cli/commands/summarise/group.py +51 -0
- metabintools/cli/commands/trim.py +54 -0
- metabintools/cli/commands/view.py +114 -0
- metabintools/dataclasses/annotation.py +77 -0
- metabintools/dataclasses/bin.py +274 -0
- metabintools/dataclasses/binset.py +216 -0
- metabintools/dataclasses/contig.py +69 -0
- metabintools/ena_taxonomy/ena_taxonomy.py +186 -0
- metabintools/enums.py +60 -0
- metabintools/export/binset_exporter.py +252 -0
- metabintools/import_data/annotation.py +100 -0
- metabintools/import_data/assembly.py +56 -0
- metabintools/import_data/binset.py +55 -0
- metabintools/import_data/coverage.py +40 -0
- metabintools/import_data/quality.py +103 -0
- metabintools/import_data/taxonomy.py +59 -0
- metabintools/operations/__init__.py +5 -0
- metabintools/operations/merge.py +45 -0
- metabintools/operations/rename.py +57 -0
- metabintools/query/query_parser.py +211 -0
- metabintools-0.2.0.dist-info/METADATA +276 -0
- metabintools-0.2.0.dist-info/RECORD +46 -0
- metabintools-0.2.0.dist-info/WHEEL +4 -0
- metabintools-0.2.0.dist-info/entry_points.txt +4 -0
- metabintools-0.2.0.dist-info/licenses/LICENSE +21 -0
metabintools/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_extension(file: Path) -> str:
|
|
5
|
+
if file.suffix == ".gz":
|
|
6
|
+
return "".join(file.suffixes[-2:])
|
|
7
|
+
return file.suffix
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_basename(file: Path | str) -> str:
|
|
11
|
+
if isinstance(file, str):
|
|
12
|
+
file = Path(file)
|
|
13
|
+
|
|
14
|
+
if file.suffix == ".gz":
|
|
15
|
+
return file.name.rsplit(".", 2)[0]
|
|
16
|
+
else:
|
|
17
|
+
return file.name.rsplit(".", 1)[0]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def find_binfiles(directory: Path) -> list[Path]:
|
|
21
|
+
return [
|
|
22
|
+
p
|
|
23
|
+
for p in directory.glob("*")
|
|
24
|
+
if get_extension(p)
|
|
25
|
+
in {".fa", ".fna", ".fasta", ".fa.gz", ".fna.gz", ".fasta.gz"}
|
|
26
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from metabintools.dataclasses.contig import Contig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CalculateBinStatistics:
|
|
5
|
+
@staticmethod
|
|
6
|
+
def bin_size(contig_dict: dict[str, Contig]) -> int:
|
|
7
|
+
"""Calculate the total size of the bin."""
|
|
8
|
+
return sum(contig.sequence_length for contig in contig_dict.values())
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def bin_n50(contig_dict: dict[str, Contig]) -> int:
|
|
12
|
+
"""Calculate the N50 of the bin."""
|
|
13
|
+
lengths = [contig.sequence_length for contig in contig_dict.values()]
|
|
14
|
+
lengths.sort(reverse=True)
|
|
15
|
+
total = sum(lengths)
|
|
16
|
+
half = total // 2
|
|
17
|
+
for length in lengths:
|
|
18
|
+
if total >= half:
|
|
19
|
+
return length
|
|
20
|
+
total -= length
|
|
21
|
+
return 0
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def bin_longest_contig(contig_dict: dict[str, Contig]) -> int:
|
|
25
|
+
"""Calculate the length of the longest contig in the bin."""
|
|
26
|
+
return max(contig.sequence_length for contig in contig_dict.values())
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def bin_n_circular(contig_dict: dict[str, Contig]) -> int:
|
|
30
|
+
"""Calculate the number of circular contigs in the bin."""
|
|
31
|
+
return sum(
|
|
32
|
+
1 for contig in contig_dict.values() if contig.topology == "circular"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def bin_n_unique_trnas(contig_dict: dict[str, Contig]) -> int:
|
|
37
|
+
"""Extract unique tRNA products from the bin."""
|
|
38
|
+
trnas = [
|
|
39
|
+
trna for contig in contig_dict.values() for trna in (contig.trnas or [])
|
|
40
|
+
]
|
|
41
|
+
return len(list(set(trnas)))
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def bin_has_5s(contig_dict: dict[str, Contig]) -> bool:
|
|
45
|
+
"""Check if the bin contains 5S rRNA."""
|
|
46
|
+
return any(contig.has_5s for contig in contig_dict.values())
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def bin_has_16s(contig_dict: dict[str, Contig]) -> bool:
|
|
50
|
+
"""Check if the bin contains 16S rRNA."""
|
|
51
|
+
return any(contig.has_16s for contig in contig_dict.values())
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def bin_has_23s(contig_dict: dict[str, Contig]) -> bool:
|
|
55
|
+
"""Check if the bin contains 23S rRNA."""
|
|
56
|
+
return any(contig.has_23s for contig in contig_dict.values())
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def bin_coverage(contig_dict: dict[str, Contig]) -> float | None:
|
|
60
|
+
"""Calculate the average coverage of the bin."""
|
|
61
|
+
coverages = [
|
|
62
|
+
contig.coverage
|
|
63
|
+
for contig in contig_dict.values()
|
|
64
|
+
if contig.coverage is not None
|
|
65
|
+
]
|
|
66
|
+
if len(coverages) == len(contig_dict):
|
|
67
|
+
return sum(coverages) / len(coverages)
|
|
68
|
+
return None
|
|
File without changes
|
metabintools/cli/cli.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from loguru import logger
|
|
5
|
+
|
|
6
|
+
from metabintools.cli.commands.export import export_group
|
|
7
|
+
from metabintools.cli.commands.import_data import import_group
|
|
8
|
+
from metabintools.cli.commands.merge import merge
|
|
9
|
+
from metabintools.cli.commands.rename import rename_bins
|
|
10
|
+
from metabintools.cli.commands.summarise import summarise_group
|
|
11
|
+
from metabintools.cli.commands.trim import trim
|
|
12
|
+
from metabintools.cli.commands.view import view_bins
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group(context_settings={"max_content_width": 200})
|
|
16
|
+
@click.version_option()
|
|
17
|
+
@click.option(
|
|
18
|
+
"--log-level",
|
|
19
|
+
default="INFO",
|
|
20
|
+
help="Set the log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
|
|
21
|
+
)
|
|
22
|
+
def cli(log_level):
|
|
23
|
+
logger.remove()
|
|
24
|
+
logger.add(
|
|
25
|
+
sys.stderr,
|
|
26
|
+
format="[{time:HH:mm:ss}] | metabintools | {level} - {message}",
|
|
27
|
+
level=log_level,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
cli.add_command(import_group, name="import")
|
|
32
|
+
cli.add_command(export_group, name="export")
|
|
33
|
+
cli.add_command(summarise_group, name="summarise")
|
|
34
|
+
cli.add_command(view_bins, name="view")
|
|
35
|
+
cli.add_command(merge, name="merge")
|
|
36
|
+
cli.add_command(trim, name="trim")
|
|
37
|
+
cli.add_command(rename_bins, name="rename")
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
cli()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from metabintools.cli.commands.export.contig2bin import contig2bin
|
|
4
|
+
from metabintools.cli.commands.export.fasta import fasta
|
|
5
|
+
from metabintools.cli.commands.export.gff import gff
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.group()
|
|
9
|
+
def export_group():
|
|
10
|
+
"""
|
|
11
|
+
Tools for exporting data from a BINS file.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export_group.add_command(contig2bin)
|
|
16
|
+
export_group.add_command(fasta)
|
|
17
|
+
export_group.add_command(gff)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import IO
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from metabintools.dataclasses.binset import BinSet
|
|
8
|
+
from metabintools.export.binset_exporter import BinSetExporter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command("contig2bin")
|
|
12
|
+
@click.option(
|
|
13
|
+
"--output",
|
|
14
|
+
"-o",
|
|
15
|
+
type=click.Path(dir_okay=False, file_okay=True, exists=False),
|
|
16
|
+
required=True,
|
|
17
|
+
default=".",
|
|
18
|
+
help="Output file path for the contig2bin mapping",
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"-g",
|
|
22
|
+
"--group",
|
|
23
|
+
type=str,
|
|
24
|
+
help="Write bins from a specific group (if not specified, all bins are included)",
|
|
25
|
+
)
|
|
26
|
+
@click.argument(
|
|
27
|
+
"binfile",
|
|
28
|
+
type=click.File("rb"),
|
|
29
|
+
nargs=1,
|
|
30
|
+
required=True,
|
|
31
|
+
default="-",
|
|
32
|
+
help="Input binfile to export (use '-' for stdin)",
|
|
33
|
+
)
|
|
34
|
+
def contig2bin(
|
|
35
|
+
binfile: IO,
|
|
36
|
+
output: str,
|
|
37
|
+
group: str | None = None,
|
|
38
|
+
):
|
|
39
|
+
"""Write stored bin annotations to a set of GFF files.
|
|
40
|
+
|
|
41
|
+
BINFILE: Path to the binfile to write GFFs for.
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
logger.info(f"Reading binfile from {binfile.name}...")
|
|
45
|
+
binset = BinSet.read_binfile(binfile)
|
|
46
|
+
|
|
47
|
+
output_path = Path(output)
|
|
48
|
+
logger.info(f"Writing contig2bin mapping to {output_path}")
|
|
49
|
+
|
|
50
|
+
BinSetExporter(binset).export_contig2bin(
|
|
51
|
+
path=output_path,
|
|
52
|
+
group=group,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
logger.info("Contig2bin export completed successfully.")
|
|
56
|
+
|
|
57
|
+
except click.ClickException:
|
|
58
|
+
raise
|
|
59
|
+
except OSError as e:
|
|
60
|
+
logger.error(f"File I/O error: {e}")
|
|
61
|
+
raise click.ClickException(f"File I/O error: {e}")
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import IO
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from metabintools.dataclasses.binset import BinSet
|
|
8
|
+
from metabintools.export.binset_exporter import BinSetExporter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command("fasta")
|
|
12
|
+
@click.option(
|
|
13
|
+
"--compress",
|
|
14
|
+
"-z",
|
|
15
|
+
"compress",
|
|
16
|
+
is_flag=True,
|
|
17
|
+
help="(optional) Compress the output using zstd.",
|
|
18
|
+
)
|
|
19
|
+
@click.option(
|
|
20
|
+
"--outdir",
|
|
21
|
+
"-o",
|
|
22
|
+
type=click.Path(dir_okay=True, file_okay=False),
|
|
23
|
+
required=True,
|
|
24
|
+
default=".",
|
|
25
|
+
help="Output directory for FASTA files",
|
|
26
|
+
)
|
|
27
|
+
@click.option(
|
|
28
|
+
"--preserve-headers",
|
|
29
|
+
"-h",
|
|
30
|
+
is_flag=True,
|
|
31
|
+
help="(optional) Preserve headers in the output FASTA file.",
|
|
32
|
+
)
|
|
33
|
+
@click.option(
|
|
34
|
+
"--group-fasta",
|
|
35
|
+
"-g",
|
|
36
|
+
is_flag=True,
|
|
37
|
+
help="(optional) Write each FASTA in a subdirectory named after the bin's group.",
|
|
38
|
+
)
|
|
39
|
+
@click.argument(
|
|
40
|
+
"binfile",
|
|
41
|
+
type=click.File("rb"),
|
|
42
|
+
nargs=1,
|
|
43
|
+
required=True,
|
|
44
|
+
default="-",
|
|
45
|
+
help="Input binfile to export (use '-' for stdin)",
|
|
46
|
+
)
|
|
47
|
+
def fasta(
|
|
48
|
+
binfile: IO,
|
|
49
|
+
outdir: str,
|
|
50
|
+
compress: bool = False,
|
|
51
|
+
preserve_headers: bool = False,
|
|
52
|
+
group_fasta: bool = False,
|
|
53
|
+
):
|
|
54
|
+
"""Export a BINS file to FASTA.
|
|
55
|
+
|
|
56
|
+
BINFILE: Path to the binfile to export FASTA for.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
logger.info(f"Reading binfile from {binfile.name}...")
|
|
60
|
+
binset = BinSet.read_binfile(binfile)
|
|
61
|
+
|
|
62
|
+
outdir_path = Path(outdir)
|
|
63
|
+
if not outdir_path.exists():
|
|
64
|
+
logger.info(f"Creating output directory: {outdir_path}")
|
|
65
|
+
try:
|
|
66
|
+
outdir_path.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
except OSError as e:
|
|
68
|
+
logger.error(f"Failed to create output directory: {e}")
|
|
69
|
+
raise click.ClickException(
|
|
70
|
+
f"Failed to create output directory {outdir}: {e}"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
logger.info(
|
|
74
|
+
f"Exporting {len(binset.bins) if binset.bins else 0} bin(s) to FASTA..."
|
|
75
|
+
)
|
|
76
|
+
BinSetExporter(binset).export_fasta(
|
|
77
|
+
outdir=outdir_path,
|
|
78
|
+
compress=compress,
|
|
79
|
+
preserve_headers=preserve_headers,
|
|
80
|
+
group_fasta=group_fasta,
|
|
81
|
+
)
|
|
82
|
+
logger.info("FASTA export completed successfully.")
|
|
83
|
+
|
|
84
|
+
except click.ClickException:
|
|
85
|
+
raise
|
|
86
|
+
except OSError as e:
|
|
87
|
+
logger.error(f"File I/O error: {e}")
|
|
88
|
+
raise click.ClickException(f"File I/O error: {e}")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import IO
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from metabintools.dataclasses.binset import BinSet
|
|
8
|
+
from metabintools.export.binset_exporter import BinSetExporter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command("gff")
|
|
12
|
+
@click.option(
|
|
13
|
+
"--outdir",
|
|
14
|
+
"-o",
|
|
15
|
+
type=click.Path(dir_okay=True, file_okay=False),
|
|
16
|
+
required=True,
|
|
17
|
+
default=".",
|
|
18
|
+
help="Output directory for GFF files",
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"-g",
|
|
22
|
+
"--group-gff",
|
|
23
|
+
is_flag=True,
|
|
24
|
+
help="(optional) Write each FASTA in a subdirectory named after the bin's group.",
|
|
25
|
+
)
|
|
26
|
+
@click.argument(
|
|
27
|
+
"binfile",
|
|
28
|
+
type=click.File("rb"),
|
|
29
|
+
nargs=1,
|
|
30
|
+
required=True,
|
|
31
|
+
default="-",
|
|
32
|
+
help="Input binfile to export (use '-' for stdin)",
|
|
33
|
+
)
|
|
34
|
+
def gff(
|
|
35
|
+
binfile: IO,
|
|
36
|
+
outdir: str,
|
|
37
|
+
group_gff: bool = False,
|
|
38
|
+
):
|
|
39
|
+
"""Write stored bin annotations to a set of GFF files.
|
|
40
|
+
|
|
41
|
+
BINFILE: Path to the binfile to write GFFs for.
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
logger.info(f"Reading binfile from {binfile.name}...")
|
|
45
|
+
binset = BinSet.read_binfile(binfile)
|
|
46
|
+
|
|
47
|
+
outdir_path = Path(outdir)
|
|
48
|
+
if not outdir_path.exists():
|
|
49
|
+
logger.info(f"Creating output directory: {outdir_path}")
|
|
50
|
+
try:
|
|
51
|
+
outdir_path.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
except OSError as e:
|
|
53
|
+
logger.error(f"Failed to create output directory: {e}")
|
|
54
|
+
raise click.ClickException(
|
|
55
|
+
f"Failed to create output directory {outdir}: {e}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
logger.info(
|
|
59
|
+
f"Exporting {len(binset.bins) if binset.bins else 0} bin(s) to GFF..."
|
|
60
|
+
)
|
|
61
|
+
BinSetExporter(binset).export_gff(
|
|
62
|
+
outdir=outdir_path,
|
|
63
|
+
group_gff=group_gff,
|
|
64
|
+
)
|
|
65
|
+
logger.info("GFF export completed successfully.")
|
|
66
|
+
|
|
67
|
+
except click.ClickException:
|
|
68
|
+
raise
|
|
69
|
+
except OSError as e:
|
|
70
|
+
logger.error(f"File I/O error: {e}")
|
|
71
|
+
raise click.ClickException(f"File I/O error: {e}")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from metabintools.cli.commands.import_data.import_annotation import import_annotation
|
|
4
|
+
from metabintools.cli.commands.import_data.import_asm import import_assembly
|
|
5
|
+
from metabintools.cli.commands.import_data.import_bins import import_binset
|
|
6
|
+
from metabintools.cli.commands.import_data.import_coverage import import_coverage
|
|
7
|
+
from metabintools.cli.commands.import_data.import_quality import import_quality
|
|
8
|
+
from metabintools.cli.commands.import_data.import_taxonomy import import_taxonomy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
def import_group():
|
|
13
|
+
"""
|
|
14
|
+
Tools for importing data, both contig- and bin-level, into a BINS file.
|
|
15
|
+
|
|
16
|
+
All BINS files begin by importing an assembly via `metabintools import asm`.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
import_group.add_command(import_assembly)
|
|
21
|
+
import_group.add_command(import_binset)
|
|
22
|
+
import_group.add_command(import_annotation)
|
|
23
|
+
import_group.add_command(import_coverage)
|
|
24
|
+
import_group.add_command(import_taxonomy)
|
|
25
|
+
import_group.add_command(import_quality)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import IO
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from metabintools.dataclasses.binset import BinSet
|
|
9
|
+
from metabintools.export.binset_exporter import BinSetExporter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command("annotation")
|
|
13
|
+
@click.option(
|
|
14
|
+
"--compress",
|
|
15
|
+
"-z",
|
|
16
|
+
"compress",
|
|
17
|
+
is_flag=True,
|
|
18
|
+
help="(optional) Compress the output using zstd.",
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--overwrite",
|
|
22
|
+
type=bool,
|
|
23
|
+
help="Overwrite existing annotations if they already exist",
|
|
24
|
+
required=False,
|
|
25
|
+
)
|
|
26
|
+
@click.option("--output", "-o", type=click.File("wb"), default="-", required=False)
|
|
27
|
+
@click.argument("binfile", type=click.File("rb"), required=True, default="-")
|
|
28
|
+
@click.argument(
|
|
29
|
+
"gff",
|
|
30
|
+
type=click.Path(exists=True, dir_okay=False, file_okay=True),
|
|
31
|
+
help="The GFF file to add the annotations from.",
|
|
32
|
+
required=True,
|
|
33
|
+
)
|
|
34
|
+
def import_annotation(
|
|
35
|
+
binfile: IO, gff: str, output: IO, compress: bool = False, overwrite: bool = False
|
|
36
|
+
):
|
|
37
|
+
"""Add annotations from a GFF file to a BINS file
|
|
38
|
+
|
|
39
|
+
BINFILE: a BINS file to add the annotations to.
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
logger.info("Reading binfile...")
|
|
43
|
+
binset = BinSet.read_binfile(binfile)
|
|
44
|
+
|
|
45
|
+
logger.info(f"Adding annotations from {Path(gff).name}...")
|
|
46
|
+
try:
|
|
47
|
+
annotated_binset = binset.add_contig_annotations(
|
|
48
|
+
Path(gff), overwrite=overwrite
|
|
49
|
+
)
|
|
50
|
+
except (ValueError, KeyError) as e:
|
|
51
|
+
logger.error(f"Failed to add annotations: {e}")
|
|
52
|
+
raise click.ClickException(f"Failed to add annotations: {e}")
|
|
53
|
+
|
|
54
|
+
logger.info("Updating statistics...")
|
|
55
|
+
annotated_binset = annotated_binset.update_statistics()
|
|
56
|
+
|
|
57
|
+
logger.info("Writing binfile...")
|
|
58
|
+
BinSetExporter(annotated_binset).write_binfile(output, compress=compress)
|
|
59
|
+
logger.info("Annotation import completed successfully.")
|
|
60
|
+
|
|
61
|
+
except click.ClickException:
|
|
62
|
+
raise
|
|
63
|
+
except OSError as e:
|
|
64
|
+
logger.error(f"File I/O error: {e}")
|
|
65
|
+
raise click.ClickException(f"File I/O error: {e}")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import IO
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from metabintools.dataclasses.binset import BinSet
|
|
8
|
+
from metabintools.enums import Assembler
|
|
9
|
+
from metabintools.export.binset_exporter import BinSetExporter
|
|
10
|
+
from metabintools.import_data.assembly import parse_assembly_fasta
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command("asm")
|
|
14
|
+
@click.option(
|
|
15
|
+
"--compress",
|
|
16
|
+
"-z",
|
|
17
|
+
"compress",
|
|
18
|
+
is_flag=True,
|
|
19
|
+
help="(optional) Compress the output using zstd.",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--assembler",
|
|
23
|
+
type=click.Choice(Assembler),
|
|
24
|
+
help="Name of the assembler used to produce the assembly (optional)",
|
|
25
|
+
required=False,
|
|
26
|
+
)
|
|
27
|
+
@click.option("--output", "-o", type=click.File("wb"), default="-", required=False)
|
|
28
|
+
@click.argument(
|
|
29
|
+
"assembly", type=click.Path(exists=True, dir_okay=False, file_okay=True)
|
|
30
|
+
)
|
|
31
|
+
def import_assembly(
|
|
32
|
+
assembly: str, assembler: Assembler | None, output: IO, compress: bool = False
|
|
33
|
+
):
|
|
34
|
+
"""Import a metagenome assembly to initialise a BINS file.
|
|
35
|
+
|
|
36
|
+
ASSEMBLY: an (optionally gzip compressed) FASTA file containing the assembly.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
assembly_path = Path(assembly)
|
|
40
|
+
logger.info(f"Parsing assembly: {assembly_path.name}")
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
parsed_assembly = parse_assembly_fasta(
|
|
44
|
+
assembly_path,
|
|
45
|
+
assembler,
|
|
46
|
+
)
|
|
47
|
+
except RuntimeError as e:
|
|
48
|
+
logger.error(f"Failed to parse assembly: {e}")
|
|
49
|
+
raise click.ClickException(f"Failed to parse assembly: {e}")
|
|
50
|
+
|
|
51
|
+
logger.info(
|
|
52
|
+
f"Successfully parsed assembly with {len(parsed_assembly) if parsed_assembly else 0} contig(s)"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
binset = BinSet(contigs=parsed_assembly, bins=None)
|
|
56
|
+
|
|
57
|
+
logger.info("Writing binfile...")
|
|
58
|
+
BinSetExporter(binset).write_binfile(output, compress=compress)
|
|
59
|
+
logger.info("Assembly import completed successfully.")
|
|
60
|
+
|
|
61
|
+
except click.ClickException:
|
|
62
|
+
raise
|
|
63
|
+
except OSError as e:
|
|
64
|
+
logger.error(f"File I/O error: {e}")
|
|
65
|
+
raise click.ClickException(f"File I/O error: {e}")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import IO
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from metabintools.bin_utils import find_binfiles
|
|
8
|
+
from metabintools.dataclasses.binset import BinSet
|
|
9
|
+
from metabintools.export.binset_exporter import BinSetExporter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command("binset")
|
|
13
|
+
@click.option(
|
|
14
|
+
"--compress",
|
|
15
|
+
"-z",
|
|
16
|
+
"compress",
|
|
17
|
+
is_flag=True,
|
|
18
|
+
help="(optional) Compress the output using zstd.",
|
|
19
|
+
)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--group",
|
|
22
|
+
type=str,
|
|
23
|
+
help="Name to assign to the group of bins",
|
|
24
|
+
required=True,
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--binsplit-separator",
|
|
28
|
+
type=str,
|
|
29
|
+
help="Separator character to recover original contig names if bins were generated by SemiBin2, VAMB, or another binsplitting binner (optional)",
|
|
30
|
+
required=False,
|
|
31
|
+
)
|
|
32
|
+
@click.option("--output", "-o", type=click.File("wb"), default="-", required=False)
|
|
33
|
+
@click.argument("binfile", type=click.File("rb"), required=True, default="-")
|
|
34
|
+
@click.argument(
|
|
35
|
+
"fasta",
|
|
36
|
+
type=click.Path(exists=True, dir_okay=True, file_okay=True),
|
|
37
|
+
nargs=-1,
|
|
38
|
+
required=True,
|
|
39
|
+
help="Bin FASTA files, or directories containing bin FASTA files",
|
|
40
|
+
)
|
|
41
|
+
def import_binset(
|
|
42
|
+
binfile: IO,
|
|
43
|
+
fasta: list[str],
|
|
44
|
+
group: str,
|
|
45
|
+
output: IO,
|
|
46
|
+
compress: bool = False,
|
|
47
|
+
binsplit_separator: str | None = None,
|
|
48
|
+
):
|
|
49
|
+
"""Add a set of bins to a BINS file.
|
|
50
|
+
|
|
51
|
+
BINFILE: a BINS file to add the bins to
|
|
52
|
+
FASTA: a list of bin FASTA files, or alternatively a directories containing bin FASTA files
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
logger.info("Reading binfile...")
|
|
56
|
+
binset = BinSet.read_binfile(binfile)
|
|
57
|
+
|
|
58
|
+
logger.info(f"Locating bin files from {len(fasta)} input(s)...")
|
|
59
|
+
bin_directories_files = [
|
|
60
|
+
find_binfiles(Path(dir)) for dir in fasta if Path(dir).is_dir()
|
|
61
|
+
]
|
|
62
|
+
bins_direct_files = [Path(file) for file in fasta if Path(file).is_file()]
|
|
63
|
+
bin_files = [
|
|
64
|
+
item for sublist in bin_directories_files for item in sublist
|
|
65
|
+
] + bins_direct_files
|
|
66
|
+
|
|
67
|
+
if not bin_files:
|
|
68
|
+
logger.error("No bin FASTA files found in the provided paths")
|
|
69
|
+
raise click.ClickException("No bin FASTA files found in the provided paths")
|
|
70
|
+
|
|
71
|
+
logger.info(f"Found {len(bin_files)} bin file(s) to import")
|
|
72
|
+
|
|
73
|
+
logger.info(f"Adding {len(bin_files)} bin(s) to binset with group '{group}'...")
|
|
74
|
+
try:
|
|
75
|
+
out_binset = binset.add_bins_from_fasta(
|
|
76
|
+
bin_files,
|
|
77
|
+
group=group,
|
|
78
|
+
binsplit_separator=binsplit_separator,
|
|
79
|
+
)
|
|
80
|
+
except RuntimeError as e:
|
|
81
|
+
logger.error(f"Failed to add bins: {e}")
|
|
82
|
+
raise click.ClickException(f"Failed to add bins: {e}")
|
|
83
|
+
|
|
84
|
+
logger.info("Writing binfile...")
|
|
85
|
+
BinSetExporter(out_binset).write_binfile(output, compress=compress)
|
|
86
|
+
logger.info("Binset import completed successfully.")
|
|
87
|
+
|
|
88
|
+
except click.ClickException:
|
|
89
|
+
raise
|
|
90
|
+
except OSError as e:
|
|
91
|
+
logger.error(f"File I/O error: {e}")
|
|
92
|
+
raise click.ClickException(f"File I/O error: {e}")
|