From e3d8e5554ce2187201b22ca51ffd9f8493299382 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Thu, 26 May 2022 11:13:31 +0930 Subject: [PATCH 01/76] Create CONTRIBUTING.md --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e21fee7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,2 @@ +# Contributing to GraphBin project + From 12f51693690721ba613a51ebdf0c77085f4e5dd4 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Thu, 26 May 2022 11:15:22 +0930 Subject: [PATCH 02/76] TST: Add testing on develop branch --- .github/workflows/testing_python_app.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing_python_app.yml b/.github/workflows/testing_python_app.yml index ec970f0..051a0db 100644 --- a/.github/workflows/testing_python_app.yml +++ b/.github/workflows/testing_python_app.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ master ] + branches: [ develop ] pull_request: - branches: [ master ] + branches: [ develop ] jobs: From abfe8de874f8eb3b528f0023062402b5da036325 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Thu, 26 May 2022 11:23:35 +0930 Subject: [PATCH 03/76] TST: Fix test command --- .github/workflows/testing_python_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing_python_app.yml b/.github/workflows/testing_python_app.yml index 051a0db..c505c14 100644 --- a/.github/workflows/testing_python_app.yml +++ b/.github/workflows/testing_python_app.yml @@ -35,7 +35,7 @@ jobs: - name: "Generate coverage report on ${{ matrix.os }} for Python ${{ matrix.python-version }}" run: | pip install pytest pytest-cov - pytest --cov=tests --cov-report=xml --cov-append + pytest --cov=tests --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 From cf69677298a3ba7b4dfec71376f0b1b09a5babce Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Thu, 26 May 2022 11:48:31 +0930 Subject: [PATCH 04/76] TST: Update testing workflow --- .github/workflows/testing_python_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing_python_app.yml b/.github/workflows/testing_python_app.yml index c505c14..051a0db 100644 --- a/.github/workflows/testing_python_app.yml +++ b/.github/workflows/testing_python_app.yml @@ -35,7 +35,7 @@ jobs: - name: "Generate coverage report on ${{ matrix.os }} for Python ${{ matrix.python-version }}" run: | pip install pytest pytest-cov - pytest --cov=tests --cov-report=xml + pytest --cov=tests --cov-report=xml --cov-append - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 From 1207134be3f15781fdff4534656689a27f29e0b3 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 27 May 2022 12:22:16 +0930 Subject: [PATCH 05/76] DEV: refactor labelprop code --- .../utils/labelpropagation/labelprop.py | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/utils/labelpropagation/labelprop.py index 4c825a7..32e8435 100644 --- a/src/graphbin/utils/labelpropagation/labelprop.py +++ b/src/graphbin/utils/labelpropagation/labelprop.py @@ -101,14 +101,6 @@ def setup_env(self): arr.append(0.0) self.vertex_f_map.setdefault(v, arr) - def load_data_from_file(self, filename): - import ast - - with open(filename, "rb") as f: - lines = [ast.literal_eval(_.strip()) for _ in f.readlines()] - self.vertex_size = len(lines) - self.load_data_from_mem(lines) - def load_data_from_mem(self, data): self.initialize_env() self.vertex_size = len(data) @@ -121,21 +113,17 @@ def process_data_line(self, line): # unlabeled vertex if vertexLabel == 0 # i.e. [2, 1, [[1, 1.0], [3, 1.0]]] - try: - vertex_id = line[0] - vertex_label = line[1] - edges = line[2] - edge_list = [] - self.vertex_label_map.setdefault(vertex_id, vertex_label) - for edge in edges: - dest_vertex_id = int(edge[0]) - edge_weight = float(edge[1]) - edge_list.append(Edge(vertex_id, dest_vertex_id, edge_weight)) - self.vertex_adj_map.setdefault(vertex_id, edge_list) - - except Exception as e: - - raise Exception("Coundn't parse vertex from line") + vertex_id = line[0] + vertex_label = line[1] + edges = line[2] + edge_list = [] + self.vertex_label_map.setdefault(vertex_id, vertex_label) + for edge in edges: + dest_vertex_id = int(edge[0]) + edge_weight = float(edge[1]) + edge_list.append(Edge(vertex_id, dest_vertex_id, edge_weight)) + self.vertex_adj_map.setdefault(vertex_id, edge_list) + ################################################################################ # Label Propagation From 31719314a75bf3cd23ec5ee5ac0f08523210530b Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 27 May 2022 12:25:41 +0930 Subject: [PATCH 06/76] TST: update tests --- tests/test_graphbin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_graphbin.py b/tests/test_graphbin.py index 0f06647..e5a3528 100644 --- a/tests/test_graphbin.py +++ b/tests/test_graphbin.py @@ -47,6 +47,12 @@ def exec_command(cmnd, stdout=subprocess.PIPE, stderr=subprocess.PIPE): return out.decode("utf8") if out is not None else None +def test_graphbin_version(): + """test graphbin version""" + cmd = "graphbin --version" + exec_command(cmd) + + def test_graphbin_on_spades_dataset(tmp_dir): """test graphbin on spades assembly""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" From 75d68384d24041d4127e9a00ce36e77cf03ec522 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 27 May 2022 12:26:03 +0930 Subject: [PATCH 07/76] TST: Add new tests for arguments and utils --- tests/test_arguments.py | 147 ++++++++++++++++++++++++++++++++ tests/test_bidirectional_map.py | 33 +++++++ 2 files changed, 180 insertions(+) create mode 100644 tests/test_arguments.py create mode 100644 tests/test_bidirectional_map.py diff --git a/tests/test_arguments.py b/tests/test_arguments.py new file mode 100644 index 0000000..1e3ff8e --- /dev/null +++ b/tests/test_arguments.py @@ -0,0 +1,147 @@ +import subprocess +from pathlib import Path + +import pytest + +__author__ = "Vijini Mallawaarachchi" +__copyright__ = "Copyright 2019-2022, GraphBin Project" +__credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] +__license__ = "BSD-3" +__version__ = "1.6.0" +__maintainer__ = "Vijini Mallawaarachchi" +__email__ = "vijini.mallawaarachchi@anu.edu.au" +__status__ = "Development" + + +TEST_ROOTDIR = Path(__file__).parent +EXEC_ROOTDIR = Path(__file__).parent.parent + + +@pytest.fixture(scope="session") +def tmp_dir(tmpdir_factory): + return tmpdir_factory.mktemp("tmp") + + +@pytest.fixture(autouse=True) +def workingdir(tmp_dir, monkeypatch): + """set the working directory for all tests""" + monkeypatch.chdir(tmp_dir) + + +def exec_wrong_command(cmnd, stdout=subprocess.PIPE, stderr=subprocess.PIPE): + proc = subprocess.Popen(cmnd, shell=True, stdout=stdout, stderr=stderr) + out, err = proc.communicate() + if proc.returncode == 1: + return out.decode("utf8") if out is not None else None + + +def test_graphbin_assembler(tmp_dir): + """test graphbin on wrong assembler""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffolds.gfa" + contigs = dir_name / "contigs.fasta" + paths = dir_name / "contigs.paths" + binned = dir_name / "initial_binning_res.csv" + cmd = f"graphbin --assembler spa --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir}" + exec_wrong_command(cmd) + +def test_graphbin_graph(tmp_dir): + """test graphbin on wrong graph file""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffold.gfa" + contigs = dir_name / "contigs.fasta" + paths = dir_name / "contigs.paths" + binned = dir_name / "initial_binning_res.csv" + cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir}" + exec_wrong_command(cmd) + +def test_graphbin_contigs(tmp_dir): + """test graphbin on wrong contigs file""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffolds.gfa" + contigs = dir_name / "contig.fasta" + paths = dir_name / "contigs.paths" + binned = dir_name / "initial_binning_res.csv" + cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir}" + exec_wrong_command(cmd) + +def test_graphbin_paths(tmp_dir): + """test graphbin on wrong paths file""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffolds.gfa" + contigs = dir_name / "contigs.fasta" + paths = dir_name / "contig.paths" + binned = dir_name / "initial_binning_res.csv" + cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir}" + exec_wrong_command(cmd) + +def test_graphbin_binned(tmp_dir): + """test graphbin on wrong binned result file""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffolds.gfa" + contigs = dir_name / "contigs.fasta" + paths = dir_name / "contigs.paths" + binned = dir_name / "initial_binning.csv" + cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir}" + exec_wrong_command(cmd) + +def test_graphbin_spades_path(tmp_dir): + """test graphbin spades without paths""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffolds.gfa" + contigs = dir_name / "contigs.fasta" + binned = dir_name / "initial_binning_res.csv" + cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --binned {binned} --output {tmp_dir}" + exec_wrong_command(cmd) + +def test_graphbin_no_contigs(tmp_dir): + """test graphbin with no contigs""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffolds.gfa" + paths = dir_name / "contigs.paths" + binned = dir_name / "initial_binning_res.csv" + cmd = f"graphbin --assembler spades --graph {graph} --paths {paths} --binned {binned} --output {tmp_dir}" + exec_wrong_command(cmd) + +def test_graphbin_prefix(tmp_dir): + """test graphbin with prefix""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffolds.gfa" + contigs = dir_name / "contigs.fasta" + paths = dir_name / "contigs.paths" + binned = dir_name / "initial_binning_res.csv" + cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir} --prefix test" + exec_wrong_command(cmd) + +def test_graphbin_delimiter(tmp_dir): + """test graphbin with wrong delimiter""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffolds.gfa" + contigs = dir_name / "contigs.fasta" + paths = dir_name / "contigs.paths" + binned = dir_name / "initial_binning_res.csv" + delimiter = "." + cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir} --delimiter {delimiter}" + exec_wrong_command(cmd) + +def test_graphbin_max_iteration(tmp_dir): + """test graphbin with wrong max_iteration""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffolds.gfa" + contigs = dir_name / "contigs.fasta" + paths = dir_name / "contigs.paths" + binned = dir_name / "initial_binning_res.csv" + max_iteration = -10 + cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir} --max_iteration {max_iteration}" + exec_wrong_command(cmd) + +def test_graphbin_diff_threshold(tmp_dir): + """test graphbin with wrong diff_threshold""" + dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" + graph = dir_name / "assembly_graph_with_scaffolds.gfa" + contigs = dir_name / "contigs.fasta" + paths = dir_name / "contigs.paths" + binned = dir_name / "initial_binning_res.csv" + diff_threshold = -10 + cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir} --max_iteration {diff_threshold}" + exec_wrong_command(cmd) \ No newline at end of file diff --git a/tests/test_bidirectional_map.py b/tests/test_bidirectional_map.py new file mode 100644 index 0000000..bd82335 --- /dev/null +++ b/tests/test_bidirectional_map.py @@ -0,0 +1,33 @@ +import subprocess +import pytest + +from pathlib import Path +from graphbin.utils.bidirectionalmap.bidirectionalmap import * + +__author__ = "Vijini Mallawaarachchi" +__copyright__ = "Copyright 2019-2022, GraphBin Project" +__credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] +__license__ = "BSD-3" +__version__ = "1.6.0" +__maintainer__ = "Vijini Mallawaarachchi" +__email__ = "vijini.mallawaarachchi@anu.edu.au" +__status__ = "Development" + + +def test_bidirectional_map_insert(): + try: + my_map = BidirectionalMap() + + my_map[0] = 56 + my_map[7] = 56 + + except BidirectionalError: + print("BidirectionalError") + + +def test_bidirectional_map_delete(): + my_map = BidirectionalMap() + + my_map[0] = 56 + my_map[2] = 57 + my_map._del_item(2) \ No newline at end of file From d460e16f8f243a0f38e52f88a2cc763dfa08ca52 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 27 May 2022 12:27:02 +0930 Subject: [PATCH 08/76] TST: update testing workflow --- .github/workflows/testing_python_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing_python_app.yml b/.github/workflows/testing_python_app.yml index 051a0db..360f234 100644 --- a/.github/workflows/testing_python_app.yml +++ b/.github/workflows/testing_python_app.yml @@ -35,7 +35,7 @@ jobs: - name: "Generate coverage report on ${{ matrix.os }} for Python ${{ matrix.python-version }}" run: | pip install pytest pytest-cov - pytest --cov=tests --cov-report=xml --cov-append + pytest --cov=graphbin --cov-report=xml --cov-append - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 From cd63d4ef554cd39d4837c86ccdade623962d22d2 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 27 May 2022 12:53:29 +0930 Subject: [PATCH 09/76] Add issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 30 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..4a9ffde --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behaviour, including the +1. Command executed +2. Error message + +**Expected behaviour** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. Ubuntu] + - OS version [e.g. 20.04 LTS] + - Python version [e.g. 3.7] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From d7d03bba6f8b02d512a1999f4b2945ae3e5f219e Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Thu, 2 Jun 2022 10:15:33 +0930 Subject: [PATCH 10/76] DOC: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65cd419..55461a0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs [![CI](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml/badge.svg)](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml) -[![codecov](https://codecov.io/gh/metagentools/GraphBin/branch/master/graph/badge.svg?token=0S310F6QXJ)](https://codecov.io/gh/metagentools/GraphBin) +[![codecov](https://codecov.io/gh/metagentools/GraphBin/branch/develop/graph/badge.svg?token=0S310F6QXJ)](https://codecov.io/gh/metagentools/GraphBin) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Anaconda-Server Badge](https://anaconda.org/bioconda/graphbin/badges/installer/conda.svg)](https://anaconda.org/bioconda/graphbin) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/Vini2/GraphBin.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Vini2/GraphBin/context:python) From 6983457c1fa04d84c144a8c24240bf82de3812fc Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Thu, 2 Jun 2022 10:15:47 +0930 Subject: [PATCH 11/76] DOC: Update CONTRIBUTING.md --- CONTRIBUTING.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e21fee7..63ce799 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,2 +1,66 @@ # Contributing to GraphBin project +We love to have your contributions to the GraphBin, whether it's: +* Reporting a bug +* Discussing the current state of the code +* Submitting a fix +* Proposing new features + +## Clone and install GraphBin onto your machine + +On GitHub, [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the GraphBin repository and clone it to your machine. + +``` +# clone repository to your local machine +git clone https://github.com/metagentools/GraphBin.git +``` + +Move to the GraphBin directory, install via [flit](https://pypi.org/project/flit/). + +``` +# go to repo direcotry +cd GraphBin + +# install flit +pip install flit + +# install graphbin via flit +flit install -s --python `which python` +``` + +## Test GraphBin installation + +Run the following command and the all the tests should pass. + +``` +pytest +``` + +## Coding Style + +We adhere to the [PEP 8](https://peps.python.org/pep-0008/) style guide. + +Before committing, run [`black`](https://pypi.org/project/black/) and [`isort`](https://pypi.org/project/isort/) before committing. + +## Report bugs using Github's issues + +We use GitHub issues to track public bugs. Report a bug by opening a new issue in GitHub [issues](https://github.com/metagentools/GraphBin/issues). You will get to select between templates for bug report and feature request. + +## Committing code + +Once you have finished coding and all the tests pass, commit your code and make a pull request. Make sure to follow the commit style of [c3dev](https://github.com/cogent3/c3dev/wiki#style-for-commit-messages). + +``` +git commit -m "" +git push +``` + +Your contribution will be reviewed before accepting it. + +## License + +By contributing, you agree that your contributions will be licensed under the BSD-3 License. + +## References + +This document was adapted from the open-source contribution guidelines for [Transcriptase](https://github.com/briandk/transcriptase-atom/blob/master/CONTRIBUTING.md) and [c3dev](https://github.com/cogent3/c3dev/wiki/How-to-Contribute-Code). \ No newline at end of file From a0b62ef0b2a7d4f3ff7a1304b9e8be7b4d15ae06 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 3 Jun 2022 09:40:37 +0930 Subject: [PATCH 12/76] DOC: Update documentation --- CONTRIBUTING.md | 5 ++--- README.md | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63ce799..aaa7478 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,7 @@ # Contributing to GraphBin project -We love to have your contributions to the GraphBin, whether it's: +We love to have your contributions to the GraphBin project, whether it's: * Reporting a bug -* Discussing the current state of the code * Submitting a fix * Proposing new features @@ -30,7 +29,7 @@ flit install -s --python `which python` ## Test GraphBin installation -Run the following command and the all the tests should pass. +Use the following command to run [pytest](https://docs.pytest.org/en/7.1.x/) and the all the tests should pass. ``` pytest diff --git a/README.md b/README.md index 55461a0..f81bc2c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ [![CI](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml/badge.svg)](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml) [![codecov](https://codecov.io/gh/metagentools/GraphBin/branch/develop/graph/badge.svg?token=0S310F6QXJ)](https://codecov.io/gh/metagentools/GraphBin) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -[![Anaconda-Server Badge](https://anaconda.org/bioconda/graphbin/badges/installer/conda.svg)](https://anaconda.org/bioconda/graphbin) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/Vini2/GraphBin.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Vini2/GraphBin/context:python) [![Documentation Status](https://readthedocs.org/projects/graphbin/badge/?version=latest)](https://graphbin.readthedocs.io/en/latest/?badge=latest) From 6c4f75b86fd22b9a2080cb5419b25d6a55b1a8c8 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 3 Jun 2022 09:48:22 +0930 Subject: [PATCH 13/76] DOC: Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aaa7478..66d3a94 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,14 +7,14 @@ We love to have your contributions to the GraphBin project, whether it's: ## Clone and install GraphBin onto your machine -On GitHub, [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the GraphBin repository and clone it to your machine. +On GitHub, [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the GraphBin repository and [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) it to your machine. ``` # clone repository to your local machine git clone https://github.com/metagentools/GraphBin.git ``` -Move to the GraphBin directory, install via [flit](https://pypi.org/project/flit/). +Move to the GraphBin directory and install GraphBin via [flit](https://pypi.org/project/flit/). ``` # go to repo direcotry @@ -39,7 +39,7 @@ pytest We adhere to the [PEP 8](https://peps.python.org/pep-0008/) style guide. -Before committing, run [`black`](https://pypi.org/project/black/) and [`isort`](https://pypi.org/project/isort/) before committing. +Before committing, make sure to run [`black`](https://pypi.org/project/black/) and [`isort`](https://pypi.org/project/isort/). ## Report bugs using Github's issues From 179f54fe802e9314b47657ac71656c72a429cc92 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 3 Jun 2022 09:54:05 +0930 Subject: [PATCH 14/76] MAINT: Code refactoring and formatting --- src/graphbin/__init__.py | 5 ++--- src/graphbin/support/gfa2fasta.py | 1 + src/graphbin/support/prep_result.py | 1 + .../support/visualise_result_Flye_Canu_Miniasm.py | 1 + src/graphbin/support/visualise_result_MEGAHIT.py | 1 + src/graphbin/support/visualise_result_SGA.py | 1 + src/graphbin/support/visualise_result_SPAdes.py | 1 + src/graphbin/utils/graphbin_Canu.py | 1 + src/graphbin/utils/graphbin_Flye.py | 1 + src/graphbin/utils/graphbin_MEGAHIT.py | 1 + src/graphbin/utils/graphbin_Miniasm.py | 1 + src/graphbin/utils/graphbin_Options.py | 1 + src/graphbin/utils/graphbin_SGA.py | 1 + src/graphbin/utils/graphbin_SPAdes.py | 2 ++ src/graphbin/utils/labelpropagation/labelprop.py | 2 +- tests/test_arguments.py | 14 +++++++++++++- tests/test_bidirectional_map.py | 7 +++++-- tests/test_graphbin.py | 2 ++ 18 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index 6b2bdb3..acf2792 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -15,6 +15,7 @@ graphbin_SPAdes, ) + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] @@ -52,9 +53,7 @@ def main(): help="path to the contigs.paths file, only needed for SPAdes", ) parser.add_argument( - "--contigs", - default=None, - help="path to the contigs.fa file.", + "--contigs", default=None, help="path to the contigs.fa file.", ) parser.add_argument( "--delimiter", diff --git a/src/graphbin/support/gfa2fasta.py b/src/graphbin/support/gfa2fasta.py index c347720..4f16d0d 100644 --- a/src/graphbin/support/gfa2fasta.py +++ b/src/graphbin/support/gfa2fasta.py @@ -13,6 +13,7 @@ from cogent3 import make_unaligned_seqs + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/support/prep_result.py b/src/graphbin/support/prep_result.py index bf0b4bb..917bf51 100644 --- a/src/graphbin/support/prep_result.py +++ b/src/graphbin/support/prep_result.py @@ -17,6 +17,7 @@ from cogent3.parse.fasta import MinimalFastaParser + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py b/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py index e169404..23928cf 100644 --- a/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py +++ b/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py @@ -19,6 +19,7 @@ from bidirectionalmap.bidirectionalmap import BidirectionalMap from igraph import * + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/support/visualise_result_MEGAHIT.py b/src/graphbin/support/visualise_result_MEGAHIT.py index 91e633b..bce4979 100644 --- a/src/graphbin/support/visualise_result_MEGAHIT.py +++ b/src/graphbin/support/visualise_result_MEGAHIT.py @@ -21,6 +21,7 @@ from cogent3.parse.fasta import MinimalFastaParser from igraph import * + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/support/visualise_result_SGA.py b/src/graphbin/support/visualise_result_SGA.py index 74a9428..bf173c0 100644 --- a/src/graphbin/support/visualise_result_SGA.py +++ b/src/graphbin/support/visualise_result_SGA.py @@ -20,6 +20,7 @@ from bidirectionalmap.bidirectionalmap import BidirectionalMap from igraph import * + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/support/visualise_result_SPAdes.py b/src/graphbin/support/visualise_result_SPAdes.py index 1eeaa6e..d18668b 100644 --- a/src/graphbin/support/visualise_result_SPAdes.py +++ b/src/graphbin/support/visualise_result_SPAdes.py @@ -20,6 +20,7 @@ from bidirectionalmap.bidirectionalmap import BidirectionalMap from igraph import * + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/utils/graphbin_Canu.py index da33fb9..75c959b 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/utils/graphbin_Canu.py @@ -26,6 +26,7 @@ from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.labelpropagation.labelprop import LabelProp + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index e406ec1..68f21bd 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -26,6 +26,7 @@ from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.labelpropagation.labelprop import LabelProp + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/utils/graphbin_MEGAHIT.py index f4afd9a..5dc5fb7 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/utils/graphbin_MEGAHIT.py @@ -27,6 +27,7 @@ from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.labelpropagation.labelprop import LabelProp + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/utils/graphbin_Miniasm.py index 32510b8..2fd5f78 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/utils/graphbin_Miniasm.py @@ -26,6 +26,7 @@ from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.labelpropagation.labelprop import LabelProp + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/utils/graphbin_Options.py b/src/graphbin/utils/graphbin_Options.py index d493566..55055a9 100644 --- a/src/graphbin/utils/graphbin_Options.py +++ b/src/graphbin/utils/graphbin_Options.py @@ -2,6 +2,7 @@ import argparse + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/utils/graphbin_SGA.py index bcb51df..d55ddc5 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/utils/graphbin_SGA.py @@ -27,6 +27,7 @@ from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.labelpropagation.labelprop import LabelProp + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/utils/graphbin_SPAdes.py index 7a90f1c..e9de64f 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/utils/graphbin_SPAdes.py @@ -18,6 +18,7 @@ import subprocess import sys import time + from collections import defaultdict from cogent3.parse.fasta import MinimalFastaParser @@ -28,6 +29,7 @@ from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.labelpropagation.labelprop import LabelProp + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/utils/labelpropagation/labelprop.py index 32e8435..f4f0f99 100644 --- a/src/graphbin/utils/labelpropagation/labelprop.py +++ b/src/graphbin/utils/labelpropagation/labelprop.py @@ -6,6 +6,7 @@ import logging + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Lingzhe Teng", "Vijini Mallawaarachchi"] @@ -124,7 +125,6 @@ def process_data_line(self, line): edge_list.append(Edge(vertex_id, dest_vertex_id, edge_weight)) self.vertex_adj_map.setdefault(vertex_id, edge_list) - ################################################################################ # Label Propagation ################################################################################ diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 1e3ff8e..ed2c875 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -1,8 +1,10 @@ import subprocess + from pathlib import Path import pytest + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] @@ -45,6 +47,7 @@ def test_graphbin_assembler(tmp_dir): cmd = f"graphbin --assembler spa --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir}" exec_wrong_command(cmd) + def test_graphbin_graph(tmp_dir): """test graphbin on wrong graph file""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" @@ -55,6 +58,7 @@ def test_graphbin_graph(tmp_dir): cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir}" exec_wrong_command(cmd) + def test_graphbin_contigs(tmp_dir): """test graphbin on wrong contigs file""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" @@ -65,6 +69,7 @@ def test_graphbin_contigs(tmp_dir): cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir}" exec_wrong_command(cmd) + def test_graphbin_paths(tmp_dir): """test graphbin on wrong paths file""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" @@ -75,6 +80,7 @@ def test_graphbin_paths(tmp_dir): cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir}" exec_wrong_command(cmd) + def test_graphbin_binned(tmp_dir): """test graphbin on wrong binned result file""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" @@ -85,6 +91,7 @@ def test_graphbin_binned(tmp_dir): cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir}" exec_wrong_command(cmd) + def test_graphbin_spades_path(tmp_dir): """test graphbin spades without paths""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" @@ -94,6 +101,7 @@ def test_graphbin_spades_path(tmp_dir): cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --binned {binned} --output {tmp_dir}" exec_wrong_command(cmd) + def test_graphbin_no_contigs(tmp_dir): """test graphbin with no contigs""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" @@ -103,6 +111,7 @@ def test_graphbin_no_contigs(tmp_dir): cmd = f"graphbin --assembler spades --graph {graph} --paths {paths} --binned {binned} --output {tmp_dir}" exec_wrong_command(cmd) + def test_graphbin_prefix(tmp_dir): """test graphbin with prefix""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" @@ -113,6 +122,7 @@ def test_graphbin_prefix(tmp_dir): cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir} --prefix test" exec_wrong_command(cmd) + def test_graphbin_delimiter(tmp_dir): """test graphbin with wrong delimiter""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" @@ -124,6 +134,7 @@ def test_graphbin_delimiter(tmp_dir): cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir} --delimiter {delimiter}" exec_wrong_command(cmd) + def test_graphbin_max_iteration(tmp_dir): """test graphbin with wrong max_iteration""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" @@ -135,6 +146,7 @@ def test_graphbin_max_iteration(tmp_dir): cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir} --max_iteration {max_iteration}" exec_wrong_command(cmd) + def test_graphbin_diff_threshold(tmp_dir): """test graphbin with wrong diff_threshold""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" @@ -144,4 +156,4 @@ def test_graphbin_diff_threshold(tmp_dir): binned = dir_name / "initial_binning_res.csv" diff_threshold = -10 cmd = f"graphbin --assembler spades --graph {graph} --contigs {contigs} --paths {paths} --binned {binned} --output {tmp_dir} --max_iteration {diff_threshold}" - exec_wrong_command(cmd) \ No newline at end of file + exec_wrong_command(cmd) diff --git a/tests/test_bidirectional_map.py b/tests/test_bidirectional_map.py index bd82335..fa6e4ce 100644 --- a/tests/test_bidirectional_map.py +++ b/tests/test_bidirectional_map.py @@ -1,9 +1,12 @@ import subprocess -import pytest from pathlib import Path + +import pytest + from graphbin.utils.bidirectionalmap.bidirectionalmap import * + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] @@ -30,4 +33,4 @@ def test_bidirectional_map_delete(): my_map[0] = 56 my_map[2] = 57 - my_map._del_item(2) \ No newline at end of file + my_map._del_item(2) diff --git a/tests/test_graphbin.py b/tests/test_graphbin.py index e5a3528..56b3fc4 100644 --- a/tests/test_graphbin.py +++ b/tests/test_graphbin.py @@ -1,8 +1,10 @@ import subprocess + from pathlib import Path import pytest + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] From b52a3fadc8999ab93ef340d1ef8b0b9ee8478469 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 3 Jun 2022 15:30:55 +0930 Subject: [PATCH 15/76] DOC: Update documentation --- CONTRIBUTING.md | 2 ++ README.md | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 66d3a94..96f6364 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,8 @@ We love to have your contributions to the GraphBin project, whether it's: ## Clone and install GraphBin onto your machine +First, make sure you have [git](https://github.com/git-guides/install-git) installed on your machine. + On GitHub, [fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the GraphBin repository and [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) it to your machine. ``` diff --git a/README.md b/README.md index f81bc2c..370bf07 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ ## Dependencies -GraphBin installation requires python 3 (tested on Python 3.6 and 3.7). The following dependencies are required to run GraphBin and related support scripts. -* [python-igraph](https://igraph.org/python/) - version 0.7.1 -* [biopython](https://biopython.org/) - version 1.74 +GraphBin installation requires python 3 to run. The following dependencies are required to run GraphBin and related support scripts. +* [python-igraph](https://igraph.org/python/) +* [cogent3](https://cogent3.org/) * [cairocffi](https://pypi.org/project/cairocffi/) ## Installing GraphBin @@ -40,7 +40,7 @@ graphbin -h ### Using pip -You can install GraphBin using pip. +You can install GraphBin using [pip](https://pypi.org/project/graphbin/). ``` pip install graphbin From be3c2151a2fb771dd0b0f73c222a343f53e95b13 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 29 Jun 2022 20:16:31 +0930 Subject: [PATCH 16/76] DOC: Update documentation --- README.md | 12 ++++++++++-- docs/install.md | 20 +++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 370bf07..f8cfd21 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,20 @@ GraphBin installation requires python 3 to run. The following dependencies are r You can install GraphBin via [Conda](https://docs.conda.io/en/latest/). You can download [Anaconda](https://www.anaconda.com/distribution/) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html) which contains Conda. ``` -# create conda environment and install -conda create -n graphbin -c bioconda graphbin +# add channels +conda config --add channels defaults +conda config --add channels bioconda +conda config --add channels conda-forge + +# create conda environment +conda create -n graphbin # activate conda environment conda activate graphbin +# install graphbin +conda install -c bioconda graphbin + # check graphbin installation graphbin -h ``` diff --git a/docs/install.md b/docs/install.md index 06d523c..1461574 100644 --- a/docs/install.md +++ b/docs/install.md @@ -14,17 +14,31 @@ GraphBin installation requires python 3 (tested on Python 3.6 and 3.7). The foll You can install GraphBin via [Conda](https://docs.conda.io/en/latest/). You can download [Anaconda](https://www.anaconda.com/distribution/) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html) which contains Conda. -Once you have installed Conda, you can install conda directly from the bioconda distribution using the command +Now let's add conda channels so we know the locations where packages are stored. + +``` +conda config --add channels defaults +conda config --add channels bioconda +conda config --add channels conda-forge +``` + +Once you have added the channels, you can install GraphBin directly from the bioconda distribution using the command ``` conda install -c bioconda graphbin ``` -You can also create a new conda environment and install GraphBin from bioconda using the following command and activate it. +You can also create a new conda environment and install GraphBin from bioconda using the following commands. ``` -conda create -n graphbin -c bioconda graphbin +# create conda environment +conda create -n graphbin + +# activate conda environment conda activate graphbin + +# install graphbin +conda install -c bioconda graphbin ``` From 9da4ad557e786b5cdbc0914edd03a64061b5a53a Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Tue, 19 Jul 2022 15:26:23 +0930 Subject: [PATCH 17/76] DOC: Update REAMD.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f8cfd21..1f1e644 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/Vini2/GraphBin.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Vini2/GraphBin/context:python) [![Documentation Status](https://readthedocs.org/projects/graphbin/badge/?version=latest)](https://graphbin.readthedocs.io/en/latest/?badge=latest) -**GraphBin** is a NGS data-based metagenomic contig bin refinment tool that makes use of the contig connectivity information from the assembly graph to bin contigs. It utilizes the binning result of an existing binning tool and a label propagation algorithm to correct mis-binned contigs and predict the labels of contigs which are discarded due to short length. +**GraphBin** is an NGS data-based metagenomic contig bin refinement tool that makes use of the contig connectivity information from the assembly graph to bin contigs. It utilizes the binning result of an existing binning tool and a label propagation algorithm to correct mis-binned contigs and predict the labels of contigs which are discarded due to short length. **For detailed instructions on installation, usage and visualisation, please refer to the [documentation hosted at Read the Docs](https://graphbin.readthedocs.io/).** @@ -60,7 +60,7 @@ For ***development*** purposes, please clone the repository and install via [fli # clone repository to your local machine git clone https://github.com/metagentools/GraphBin.git -# go to repo direcotry +# go to repo directory cd GraphBin # install flit @@ -126,7 +126,7 @@ Vijini Mallawaarachchi, Anuradha Wickramarachchi, Yu Lin. GraphBin: Refined binn pages = {3307-3313}, year = {2020}, month = {03}, - abstract = "{The field of metagenomics has provided valuable insights into the structure, diversity and ecology within microbial communities. One key step in metagenomics analysis is to assemble reads into longer contigs which are then binned into groups of contigs that belong to different species present in the metagenomic sample. Binning of contigs plays an important role in metagenomics and most available binning algorithms bin contigs using genomic features such as oligonucleotide/k-mer composition and contig coverage. As metagenomic contigs are derived from the assembly process, they are output from the underlying assembly graph which contains valuable connectivity information between contigs that can be used for binning.We propose GraphBin, a new binning method that makes use of the assembly graph and applies a label propagation algorithm to refine the binning result of existing tools. We show that GraphBin can make use of the assembly graphs constructed from both the de Bruijn graph and the overlap-layout-consensus approach. Moreover, we demonstrate improved experimental results from GraphBin in terms of identifying mis-binned contigs and binning of contigs discarded by existing binning tools. To the best of our knowledge, this is the first time that the information from the assembly graph has been used in a tool for the binning of metagenomic contigs.The source code of GraphBin is available at https://github.com/Vini2/GraphBin.vijini.mallawaarachchi@anu.edu.au or yu.lin@anu.edu.auSupplementary data are available at Bioinformatics online.}", + abstract = "{The field of metagenomics has provided valuable insights into the structure, diversity and ecology within microbial communities. One key step in metagenomics analysis is to assemble reads into longer contigs which are then binned into groups of contigs that belong to different species present in the metagenomic sample. Binning of contigs plays an important role in metagenomics and most available binning algorithms bin contigs using genomic features such as oligonucleotide/k-mer composition and contig coverage. As metagenomic contigs are derived from the assembly process, they are output from the underlying assembly graph which contains valuable connectivity information between contigs that can be used for binning. We propose GraphBin, a new binning method that makes use of the assembly graph and applies a label propagation algorithm to refine the binning result of existing tools. We show that GraphBin can make use of the assembly graphs constructed from both the de Bruijn graph and the overlap-layout-consensus approach. Moreover, we demonstrate improved experimental results from GraphBin in terms of identifying mis-binned contigs and binning of contigs discarded by existing binning tools. To the best of our knowledge, this is the first time that the information from the assembly graph has been used in a tool for the binning of metagenomic contigs. The source code of GraphBin is available at https://github.com/Vini2/GraphBin.vijini.mallawaarachchi@anu.edu.au or yu.lin@anu.edu.auSupplementary data are available at Bioinformatics online.}", issn = {1367-4803}, doi = {10.1093/bioinformatics/btaa180}, url = {https://doi.org/10.1093/bioinformatics/btaa180}, From 166a07c1312676b912fb2481bb970d474ee1670a Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 3 Aug 2022 14:40:15 +0930 Subject: [PATCH 18/76] DOC: update requirements.txt for docs --- docs/requirements.txt | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index c6f008d..3e2bdf5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,18 @@ -jinja2<3.1.0 +jinja2==3.0.3 +mkdocs>=1.3.1 +babel>=2.9.0 +click>=7.0 +Jinja2>=2.10.2 +Markdown>=3.2.1,<3.4 +PyYAML>=5.2 +watchdog>=2.0.0 +mdx_gh_links>=0.2 +ghp-import>=1.0 +pyyaml_env_tag>=0.1 +mkdocs-redirects>=1.0.1 +importlib_metadata>=4.3 +packaging>=20.5 +mergedeep>=1.3.4 +pygments>=2.12 +pymdown-extensions +mkdocs-material From 1511d7d05526704c8986404e71f87421d7c1b422 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 6 Jan 2023 12:55:55 +1030 Subject: [PATCH 19/76] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1f1e644..6c97afe 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ [![CI](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml/badge.svg)](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml) [![codecov](https://codecov.io/gh/metagentools/GraphBin/branch/develop/graph/badge.svg?token=0S310F6QXJ)](https://codecov.io/gh/metagentools/GraphBin) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/Vini2/GraphBin.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/Vini2/GraphBin/context:python) [![Documentation Status](https://readthedocs.org/projects/graphbin/badge/?version=latest)](https://graphbin.readthedocs.io/en/latest/?badge=latest) **GraphBin** is an NGS data-based metagenomic contig bin refinement tool that makes use of the contig connectivity information from the assembly graph to bin contigs. It utilizes the binning result of an existing binning tool and a label propagation algorithm to correct mis-binned contigs and predict the labels of contigs which are discarded due to short length. From 2f221c09a6d51a9227c9fc11965e6341cb2bdc36 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 6 Jan 2023 13:02:37 +1030 Subject: [PATCH 20/76] TST: Update testing_python_app.yml --- .github/workflows/testing_python_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing_python_app.yml b/.github/workflows/testing_python_app.yml index 360f234..10abf34 100644 --- a/.github/workflows/testing_python_app.yml +++ b/.github/workflows/testing_python_app.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: os: [macos-latest, ubuntu-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10"] steps: - uses: "actions/checkout@v2" From cdc8f5860fdafc189e399b24e7f590e30783ba81 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 6 Jan 2023 13:09:35 +1030 Subject: [PATCH 21/76] DEV: Fix import in graphbin_Flye.py --- src/graphbin/utils/graphbin_Flye.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index 68f21bd..d58fadf 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -18,6 +18,8 @@ import sys import time +from collections import defaultdict + from cogent3.parse.fasta import MinimalFastaParser from igraph import * From c12616afa680c8614d563c9ccb9da46139af5d77 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 6 Jan 2023 13:32:21 +1030 Subject: [PATCH 22/76] DEV: Update version --- src/graphbin/__init__.py | 2 +- src/graphbin/utils/graphbin_Canu.py | 2 +- src/graphbin/utils/graphbin_Flye.py | 2 +- src/graphbin/utils/graphbin_Func.py | 2 +- src/graphbin/utils/graphbin_MEGAHIT.py | 2 +- src/graphbin/utils/graphbin_Miniasm.py | 2 +- src/graphbin/utils/graphbin_Options.py | 2 +- src/graphbin/utils/graphbin_SGA.py | 2 +- src/graphbin/utils/graphbin_SPAdes.py | 2 +- src/graphbin/utils/labelpropagation/labelprop.py | 2 +- tests/test_arguments.py | 2 +- tests/test_bidirectional_map.py | 2 +- tests/test_graphbin.py | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index acf2792..3082008 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -20,7 +20,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/utils/graphbin_Canu.py index 75c959b..3ee1a51 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/utils/graphbin_Canu.py @@ -31,7 +31,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index d58fadf..1aab35b 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -33,7 +33,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Func.py b/src/graphbin/utils/graphbin_Func.py index 8d9fbf0..3e3c01e 100644 --- a/src/graphbin/utils/graphbin_Func.py +++ b/src/graphbin/utils/graphbin_Func.py @@ -4,7 +4,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/utils/graphbin_MEGAHIT.py index 5dc5fb7..98b8e57 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/utils/graphbin_MEGAHIT.py @@ -32,7 +32,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/utils/graphbin_Miniasm.py index 2fd5f78..0a31d13 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/utils/graphbin_Miniasm.py @@ -31,7 +31,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Options.py b/src/graphbin/utils/graphbin_Options.py index 55055a9..d43a795 100644 --- a/src/graphbin/utils/graphbin_Options.py +++ b/src/graphbin/utils/graphbin_Options.py @@ -7,7 +7,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/utils/graphbin_SGA.py index d55ddc5..44343dd 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/utils/graphbin_SGA.py @@ -32,7 +32,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/utils/graphbin_SPAdes.py index e9de64f..5ef4355 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/utils/graphbin_SPAdes.py @@ -34,7 +34,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/utils/labelpropagation/labelprop.py index f4f0f99..fcd169b 100644 --- a/src/graphbin/utils/labelpropagation/labelprop.py +++ b/src/graphbin/utils/labelpropagation/labelprop.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Lingzhe Teng", "Vijini Mallawaarachchi"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/tests/test_arguments.py b/tests/test_arguments.py index ed2c875..c1c072e 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" diff --git a/tests/test_bidirectional_map.py b/tests/test_bidirectional_map.py index fa6e4ce..80055e8 100644 --- a/tests/test_bidirectional_map.py +++ b/tests/test_bidirectional_map.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" diff --git a/tests/test_graphbin.py b/tests/test_graphbin.py index 56b3fc4..57b2919 100644 --- a/tests/test_graphbin.py +++ b/tests/test_graphbin.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.6.0" +__version__ = "1.6.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" From e7b46597eaa77956cb8338d20e2fbe4429f5a952 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 6 Jan 2023 13:42:33 +1030 Subject: [PATCH 23/76] DOC: Add PyPI badge to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6c97afe..41371a1 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![CI](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml/badge.svg)](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml) [![codecov](https://codecov.io/gh/metagentools/GraphBin/branch/develop/graph/badge.svg?token=0S310F6QXJ)](https://codecov.io/gh/metagentools/GraphBin) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![PyPI version](https://badge.fury.io/py/graphbin.svg)](https://badge.fury.io/py/graphbin) [![Documentation Status](https://readthedocs.org/projects/graphbin/badge/?version=latest)](https://graphbin.readthedocs.io/en/latest/?badge=latest) **GraphBin** is an NGS data-based metagenomic contig bin refinement tool that makes use of the contig connectivity information from the assembly graph to bin contigs. It utilizes the binning result of an existing binning tool and a label propagation algorithm to correct mis-binned contigs and predict the labels of contigs which are discarded due to short length. From 60d622c3c3d5676fd4d41ca8d949ecb8e8098aa8 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 9 Jan 2023 11:50:32 +1030 Subject: [PATCH 24/76] DOC: Update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 41371a1..33bb8a3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![codecov](https://codecov.io/gh/metagentools/GraphBin/branch/develop/graph/badge.svg?token=0S310F6QXJ)](https://codecov.io/gh/metagentools/GraphBin) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![PyPI version](https://badge.fury.io/py/graphbin.svg)](https://badge.fury.io/py/graphbin) +[![Downloads](https://static.pepy.tech/badge/graphbin)](https://pepy.tech/project/graphbin) [![Documentation Status](https://readthedocs.org/projects/graphbin/badge/?version=latest)](https://graphbin.readthedocs.io/en/latest/?badge=latest) **GraphBin** is an NGS data-based metagenomic contig bin refinement tool that makes use of the contig connectivity information from the assembly graph to bin contigs. It utilizes the binning result of an existing binning tool and a label propagation algorithm to correct mis-binned contigs and predict the labels of contigs which are discarded due to short length. From acb553e9d699576bdf908c226470b2b7f7f541c2 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 9 Jan 2023 12:08:06 +1030 Subject: [PATCH 25/76] MAINT: Update issue templates --- .github/ISSUE_TEMPLATE/custom.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/custom.md diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000..48d5f81 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + From 2977b325e118ece0f55bbfd811e4aa0a7400c2e5 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 18 Jan 2023 10:23:01 +1030 Subject: [PATCH 26/76] TST: Change versions, see actions/runner-images#6384 --- .github/workflows/testing_python_app.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testing_python_app.yml b/.github/workflows/testing_python_app.yml index 10abf34..29aa06a 100644 --- a/.github/workflows/testing_python_app.yml +++ b/.github/workflows/testing_python_app.yml @@ -14,16 +14,16 @@ jobs: strategy: matrix: - os: [macos-latest, ubuntu-latest] + os: [macos-12, ubuntu-latest] python-version: ["3.8", "3.9", "3.10"] steps: - - uses: "actions/checkout@v2" + - uses: "actions/checkout@v3" with: fetch-depth: 0 # Setup env - - uses: "actions/setup-python@v2" + - uses: "actions/setup-python@v3" with: python-version: "${{ matrix.python-version }}" @@ -38,7 +38,8 @@ jobs: pytest --cov=graphbin --cov-report=xml --cov-append - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: + token: ${{ secrets.CODECOV_TOKEN }} file: coverage.xml fail_ci_if_error: true From 3cd8db2572101bd6c8ccbe19e56e8a32e9fe3e6a Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Thu, 19 Jan 2023 16:26:15 +1030 Subject: [PATCH 27/76] DEV: Create codeql.yml action --- .github/workflows/codeql.yml | 76 ++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..9898434 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,76 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "develop" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "develop" ] + schedule: + - cron: '34 3 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From 8ae58412e46cc77049aa402c9b92b1f448bf93e3 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 20 Jan 2023 13:39:53 +1030 Subject: [PATCH 28/76] DOC: Add CodeQL badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 33bb8a3..5fe9ce3 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![PyPI version](https://badge.fury.io/py/graphbin.svg)](https://badge.fury.io/py/graphbin) [![Downloads](https://static.pepy.tech/badge/graphbin)](https://pepy.tech/project/graphbin) +[![CodeQL](https://github.com/metagentools/GraphBin/actions/workflows/codeql.yml/badge.svg)](https://github.com/metagentools/GraphBin/actions/workflows/codeql.yml) [![Documentation Status](https://readthedocs.org/projects/graphbin/badge/?version=latest)](https://graphbin.readthedocs.io/en/latest/?badge=latest) **GraphBin** is an NGS data-based metagenomic contig bin refinement tool that makes use of the contig connectivity information from the assembly graph to bin contigs. It utilizes the binning result of an existing binning tool and a label propagation algorithm to correct mis-binned contigs and predict the labels of contigs which are discarded due to short length. From 121a42436f2eae29c00a57c4d473e85816a81493 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Thu, 26 Jan 2023 14:12:37 +1030 Subject: [PATCH 29/76] DOC: Update README and docs --- README.md | 5 +++-- docs/install.md | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5fe9ce3..95dfd0b 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ GraphBin installation requires python 3 to run. The following dependencies are r ### Using Conda -You can install GraphBin via [Conda](https://docs.conda.io/en/latest/). You can download [Anaconda](https://www.anaconda.com/distribution/) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html) which contains Conda. +You can install GraphBin using the [bioconda](https://anaconda.org/bioconda/graphbin) distribution. You can download +[Anaconda](https://www.anaconda.com/distribution/) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html) which contains `conda`. ``` # add channels @@ -50,7 +51,7 @@ graphbin -h ### Using pip -You can install GraphBin using [pip](https://pypi.org/project/graphbin/). +You can install GraphBin using `pip` from the [PyPI](https://pypi.org/project/graphbin/) distribution. ``` pip install graphbin diff --git a/docs/install.md b/docs/install.md index 1461574..e5012c3 100644 --- a/docs/install.md +++ b/docs/install.md @@ -12,9 +12,10 @@ GraphBin installation requires python 3 (tested on Python 3.6 and 3.7). The foll ### Method 1: conda install -You can install GraphBin via [Conda](https://docs.conda.io/en/latest/). You can download [Anaconda](https://www.anaconda.com/distribution/) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html) which contains Conda. +You can install GraphBin using the [bioconda](https://anaconda.org/bioconda/graphbin) distribution. You can download +[Anaconda](https://www.anaconda.com/distribution/) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html) which contains `conda`. -Now let's add conda channels so we know the locations where packages are stored. +Now let's add the required conda channels so we know the locations where packages are stored. ``` conda config --add channels defaults @@ -44,7 +45,7 @@ conda install -c bioconda graphbin ### Method 2: pip install -You can install GraphBin using pip. +You can install GraphBin using `pip` from the [PyPI](https://pypi.org/project/graphbin/) distribution. ``` pip install graphbin @@ -52,4 +53,4 @@ pip install graphbin After setup, check if GraphBin is properly installed by typing `graphbin -h` on the command line. You should see the usage options as shown in section [Using GraphBin](https://github.com/Vini2/GraphBin#using-graphbin) -Now let's prepare our results to run GraphBin. \ No newline at end of file +Now let's prepare our results to run GraphBin. From 8c9cb84be260fccdc5ac5f0a905b0dc65157e6de Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 30 Jan 2023 09:13:19 +1030 Subject: [PATCH 30/76] DOC: Update README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 95dfd0b..731d26e 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,13 @@ # GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs +[![Anaconda-Server Badge](https://anaconda.org/bioconda/graphbin/badges/version.svg)](https://anaconda.org/bioconda/graphbin) +[![PyPI version](https://badge.fury.io/py/graphbin.svg)](https://badge.fury.io/py/graphbin) +[![Downloads](https://static.pepy.tech/badge/graphbin)](https://pepy.tech/project/graphbin) + [![CI](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml/badge.svg)](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml) [![codecov](https://codecov.io/gh/metagentools/GraphBin/branch/develop/graph/badge.svg?token=0S310F6QXJ)](https://codecov.io/gh/metagentools/GraphBin) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -[![PyPI version](https://badge.fury.io/py/graphbin.svg)](https://badge.fury.io/py/graphbin) -[![Downloads](https://static.pepy.tech/badge/graphbin)](https://pepy.tech/project/graphbin) [![CodeQL](https://github.com/metagentools/GraphBin/actions/workflows/codeql.yml/badge.svg)](https://github.com/metagentools/GraphBin/actions/workflows/codeql.yml) [![Documentation Status](https://readthedocs.org/projects/graphbin/badge/?version=latest)](https://graphbin.readthedocs.io/en/latest/?badge=latest) From fe302369075fb760e16498fc7ea297dc10bf029d Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 13 Feb 2023 13:24:15 +1030 Subject: [PATCH 31/76] DOC: Add DOI badge to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 731d26e..3f42434 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ # GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs +[![DOI](https://img.shields.io/badge/DOI-10.1093/bioinformatics/btaa180-informational)](https://doi.org/10.1093/bioinformatics/btaa180) [![Anaconda-Server Badge](https://anaconda.org/bioconda/graphbin/badges/version.svg)](https://anaconda.org/bioconda/graphbin) [![PyPI version](https://badge.fury.io/py/graphbin.svg)](https://badge.fury.io/py/graphbin) [![Downloads](https://static.pepy.tech/badge/graphbin)](https://pepy.tech/project/graphbin) From c564b4cf2f0b854242e0873d4c5dba84aa89fa22 Mon Sep 17 00:00:00 2001 From: Anuradha Wickramarachchi Date: Sat, 4 Mar 2023 11:53:17 +1030 Subject: [PATCH 32/76] DEV: refactor code to re-use common logic --- src/graphbin/__init__.py | 4 +- src/graphbin/support/gfa2fasta.py | 3 - src/graphbin/support/prep_result.py | 4 - .../visualise_result_Flye_Canu_Miniasm.py | 3 - .../support/visualise_result_MEGAHIT.py | 2 - src/graphbin/support/visualise_result_SGA.py | 1 - .../support/visualise_result_SPAdes.py | 1 - src/graphbin/utils/graphbin_Canu.py | 529 ++------------- src/graphbin/utils/graphbin_Flye.py | 631 ++---------------- src/graphbin/utils/graphbin_Func.py | 281 ++++++++ src/graphbin/utils/graphbin_MEGAHIT.py | 562 ++-------------- src/graphbin/utils/graphbin_Miniasm.py | 524 +-------------- src/graphbin/utils/graphbin_SGA.py | 546 ++------------- src/graphbin/utils/graphbin_SPAdes.py | 600 ++--------------- .../utils/labelpropagation/labelprop.py | 1 - src/graphbin/utils/parsers/__init__.py | 49 ++ src/graphbin/utils/parsers/canu_parser.py | 226 +++++++ src/graphbin/utils/parsers/flye_parser.py | 319 +++++++++ src/graphbin/utils/parsers/megahit_parser.py | 259 +++++++ src/graphbin/utils/parsers/miniasm_parser.py | 224 +++++++ src/graphbin/utils/parsers/sga_parser.py | 238 +++++++ src/graphbin/utils/parsers/spades_parser.py | 287 ++++++++ 22 files changed, 2130 insertions(+), 3164 deletions(-) create mode 100644 src/graphbin/utils/parsers/__init__.py create mode 100644 src/graphbin/utils/parsers/canu_parser.py create mode 100644 src/graphbin/utils/parsers/flye_parser.py create mode 100644 src/graphbin/utils/parsers/megahit_parser.py create mode 100644 src/graphbin/utils/parsers/miniasm_parser.py create mode 100644 src/graphbin/utils/parsers/sga_parser.py create mode 100644 src/graphbin/utils/parsers/spades_parser.py diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index 3082008..9545bc6 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -53,7 +53,9 @@ def main(): help="path to the contigs.paths file, only needed for SPAdes", ) parser.add_argument( - "--contigs", default=None, help="path to the contigs.fa file.", + "--contigs", + default=None, + help="path to the contigs.fa file.", ) parser.add_argument( "--delimiter", diff --git a/src/graphbin/support/gfa2fasta.py b/src/graphbin/support/gfa2fasta.py index 4f16d0d..b4c39c0 100644 --- a/src/graphbin/support/gfa2fasta.py +++ b/src/graphbin/support/gfa2fasta.py @@ -84,7 +84,6 @@ # Validate prefix # --------------------------------------------------- try: - if args["prefix"] != "": if args["prefix"].endswith("_"): prefix = args["prefix"] @@ -106,12 +105,10 @@ sequences = {} with open(assembly_graph_file) as file: - for line in file.readlines(): line = line.strip() if line.startswith("S"): - strings = line.split("\t") print(strings) diff --git a/src/graphbin/support/prep_result.py b/src/graphbin/support/prep_result.py index 917bf51..f37f4ee 100644 --- a/src/graphbin/support/prep_result.py +++ b/src/graphbin/support/prep_result.py @@ -128,7 +128,6 @@ # Validate prefix # --------------------------------------------------- try: - if args["prefix"] != "": if args["prefix"].endswith("_"): prefix = args["prefix"] @@ -160,11 +159,8 @@ contig_bins = [] for bin_file in files: - if bin_file.lower().endswith((".fasta", ".fa", ".fna")): - for contig_name, _ in MinimalFastaParser(contig_bins_folder + bin_file): - line = [contig_name, str(bin_file)] contig_bins.append(line) diff --git a/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py b/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py index 23928cf..df618ac 100644 --- a/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py +++ b/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py @@ -210,7 +210,6 @@ try: # Get contig connections from .gfa file with open(assembly_graph_file) as file: - for line in file.readlines(): line = line.strip() @@ -224,7 +223,6 @@ # Identify lines with link information elif line.startswith("L"): - link = [] strings = line.split("\t") @@ -251,7 +249,6 @@ # ------------------------------- try: - # Create the graph assembly_graph = Graph() diff --git a/src/graphbin/support/visualise_result_MEGAHIT.py b/src/graphbin/support/visualise_result_MEGAHIT.py index bce4979..a0b6c42 100644 --- a/src/graphbin/support/visualise_result_MEGAHIT.py +++ b/src/graphbin/support/visualise_result_MEGAHIT.py @@ -232,10 +232,8 @@ my_map = BidirectionalMap() try: - # Get links from .gfa file with open(assembly_graph_file) as file: - for line in file.readlines(): line = line.strip() diff --git a/src/graphbin/support/visualise_result_SGA.py b/src/graphbin/support/visualise_result_SGA.py index bf173c0..88ee0d1 100644 --- a/src/graphbin/support/visualise_result_SGA.py +++ b/src/graphbin/support/visualise_result_SGA.py @@ -245,7 +245,6 @@ # ------------------------------- try: - # Create the graph assembly_graph = Graph() diff --git a/src/graphbin/support/visualise_result_SPAdes.py b/src/graphbin/support/visualise_result_SPAdes.py index d18668b..88e8ff4 100644 --- a/src/graphbin/support/visualise_result_SPAdes.py +++ b/src/graphbin/support/visualise_result_SPAdes.py @@ -219,7 +219,6 @@ path = file.readline() while name != "" and path != "": - while ";" in path: path = path[:-2] + "," + file.readline() diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/utils/graphbin_Canu.py index 3ee1a51..c6c5724 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/utils/graphbin_Canu.py @@ -11,20 +11,17 @@ graphbin_Canu.py makes use of the assembly graphs produced by Canu long read assembler. """ -import csv import logging -import os -import subprocess -import sys import time -from cogent3.parse.fasta import MinimalFastaParser -from igraph import * - -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap -from graphbin.utils.graphbin_Func import getClosestLabelledVertices +from graphbin.utils.graphbin_Func import graphbin_main from graphbin.utils.graphbin_Options import PARSER -from graphbin.utils.labelpropagation.labelprop import LabelProp +from graphbin.utils.parsers import get_initial_bin_count +from graphbin.utils.parsers.canu_parser import ( + parse_graph, + get_initial_binning_result, + write_output, +) __author__ = "Vijini Mallawaarachchi" @@ -87,505 +84,55 @@ def run(args): # Get the number of bins from the initial binning result # -------------------------------------------------------- - n_bins = 0 - - try: - all_bins_list = [] - - with open(contig_bins_file) as csvfile: - readCSV = csv.reader(csvfile, delimiter=delimiter) - for row in readCSV: - all_bins_list.append(row[1]) - - bins_list = list(set(all_bins_list)) - bins_list.sort() - - n_bins = len(bins_list) - logger.info("Number of bins available in the binning result: " + str(n_bins)) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the binning result file is provided and it is having the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Constructing the assembly graph") - - # Get the links from the .gfa file - # ----------------------------------- - - my_map = BidirectionalMap() - - node_count = 0 - - nodes = [] - - links = [] - - try: - # Get contig connections from .gfa file - with open(assembly_graph_file) as file: - - for line in file.readlines(): - line = line.strip() - - # Count the number of contigs - if line.startswith("S"): - strings = line.split("\t") - my_node = strings[1] - my_map[node_count] = my_node - nodes.append(my_node) - node_count += 1 - - # Identify lines with link information - elif line.startswith("L"): - - link = [] - strings = line.split("\t") - - if strings[1] != strings[3]: - start = strings[1] - end = strings[3] - link.append(start) - link.append(end) - links.append(link) + n_bins, bins_list = get_initial_bin_count(contig_bins_file, delimiter) - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the assembly graph file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - contigs_map = my_map - contigs_map_rev = my_map.inverse - - logger.info("Total number of contigs available: " + str(node_count)) - - ## Construct the assembly graph - # ------------------------------- - - try: - - # Create the graph - assembly_graph = Graph() - - # Create list of edges - edge_list = [] - - # Add vertices - assembly_graph.add_vertices(node_count) - - # Name vertices - for i in range(len(assembly_graph.vs)): - assembly_graph.vs[i]["id"] = i - assembly_graph.vs[i]["label"] = str(contigs_map[i]) - - # Iterate links - for link in links: - # Remove self loops - if link[0] != link[1]: - # Add edge to list of edges - edge_list.append((contigs_map_rev[link[0]], contigs_map_rev[link[1]])) - - # Add edges to the graph - assembly_graph.add_edges(edge_list) - assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the assembly graph file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + # Get assembly graph + # -------------------- + + assembly_graph, contigs_map, node_count = parse_graph(assembly_graph_file) # Get initial binning result # ---------------------------- - logger.info("Obtaining the initial binning result") - - bins = [[] for x in range(n_bins)] - - try: - with open(contig_bins_file) as contig_bins: - readCSV = csv.reader(contig_bins, delimiter=delimiter) - for row in readCSV: - contig_num = contigs_map_rev[row[0]] - - bin_num = bins_list.index(row[1]) - bins[bin_num].append(contig_num) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Remove labels of ambiguous vertices - # ------------------------------------- - - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - neighbours_have_same_label_list = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - # Get set of closest labelled vertices with distance = 1 - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - neighbours_binned = False - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - neighbours_binned = True - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - elif neighbours_binned: - neighbours_have_same_label_list.append(i) - - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - # Further remove labels of ambiguous vertices - binned_contigs = [] - - for n in range(n_bins): - binned_contigs = sorted(binned_contigs + bins[n]) - - for b in range(n_bins): - - for i in bins[b]: - - if i not in neighbours_have_same_label_list: - - my_bin = b - - # Get set of closest labelled vertices - closest_neighbours = getClosestLabelledVertices( - assembly_graph, i, binned_contigs - ) - - if len(closest_neighbours) > 0: - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label and i not in remove_labels: - if my_bin in remove_by_bin: - if ( - len(bins[my_bin]) - len(remove_by_bin[my_bin]) - >= MIN_BIN_COUNT - ): - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - logger.info("Obtaining refined binning result") - - # Get vertices which are not isolated and not in components without any labels - # ----------------------------------------------------------------------------- - - logger.info( - "Deteremining vertices which are not isolated and not in components without any labels" + bins = get_initial_binning_result( + n_bins, bins_list, contig_bins_file, contigs_map.inverse, delimiter ) - non_isolated = [] - - for i in range(node_count): - - if i not in non_isolated and i in binned_contigs: - - component = [] - component.append(i) - length = len(component) - neighbours = assembly_graph.neighbors(i, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - component = list(set(component)) - - while length != len(component): - - length = len(component) - - for j in component: - - neighbours = assembly_graph.neighbors(j, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - labelled = False - for j in component: - if j in binned_contigs: - labelled = True - break - - if labelled: - for j in component: - if j not in non_isolated: - non_isolated.append(j) - - logger.info("Number of non-isolated contigs: " + str(len(non_isolated))) - - # Run label propagation - # ----------------------- - - data = [] - - for contig in range(node_count): - - # Consider vertices that are not isolated - - if contig in non_isolated: - line = [] - line.append(contig) - - assigned = False - - for i in range(n_bins): - if contig in bins[i]: - line.append(i + 1) - assigned = True - - if not assigned: - line.append(0) - - neighbours = assembly_graph.neighbors(contig, mode=ALL) - - neighs = [] - - for neighbour in neighbours: - n = [] - n.append(neighbour) - n.append(1.0) - neighs.append(n) - - line.append(neighs) - - data.append(line) - - # Check if initial binning result consists of contigs belonging to multiple bins - - multiple_bins = False - - for item in data: - if type(item[1]) is int and type(item[2]) is int: - multiple_bins = True - break - - if multiple_bins: - logger.error( - "Initial binning result consists of contigs belonging to multiple bins. Please make sure that each contig in the initial binning result belongs to only one bin." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Label propagation - - lp = LabelProp() - - lp.load_data_from_mem(data) - - logger.info( - "Starting label propagation with eps=" - + str(diff_threshold) - + " and max_iteration=" - + str(max_iteration) - ) - - ans = lp.run(diff_threshold, max_iteration, show_log=True, clean_result=False) - - logger.info("Obtaining Label Propagation result") - - for l in ans: - for i in range(n_bins): - if l[1] == i + 1 and l[0] not in bins[i]: - bins[i].append(l[0]) - - # Remove labels of ambiguous vertices + # Run GraphBin logic # ------------------------------------- - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) + final_bins, remove_labels, non_isolated = graphbin_main( + n_bins, + bins, + bins_list, + assembly_graph, + node_count, + diff_threshold, + max_iteration, + ) elapsed_time = time.time() - start_time - logger.info("Obtaining the Final Refined Binning result") - - final_bins = {} - - for i in range(n_bins): - for contig in bins[i]: - final_bins[contig] = bins_list[i] - # Print elapsed time for the process logger.info("Elapsed time: " + str(elapsed_time) + " seconds") # Write result to output file # ----------------------------- - logger.info("Writing the Final Binning result to file") - - output_bins = [] - - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" - - if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) - - bin_files = {} - - for bin_name in set(final_bins.values()): - bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" - ) - - for label, seq in MinimalFastaParser( - contigs_file, label_to_name=lambda x: x.split()[0] - ): - - contig_num = contigs_map_rev[label] - - if contig_num in final_bins: - bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") - - # Close output files - for c in set(final_bins.values()): - bin_files[c].close() - - for b in range(len(bins)): - - for contig in bins[b]: - line = [] - line.append(str(contigs_map[contig])) - line.append(bins_list[b]) - output_bins.append(line) - - with open(output_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - for row in output_bins: - output_writer.writerow(row) - - logger.info("Final binning results can be found in " + str(output_bins_path)) - - unbinned_contigs = [] - - for i in range(node_count): - if i in remove_labels or i not in non_isolated: - line = [] - line.append(str(contigs_map[i])) - unbinned_contigs.append(line) - - if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" - - with open(unbinned_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - - for row in unbinned_contigs: - output_writer.writerow(row) - - logger.info("Unbinned contigs can be found at " + unbinned_file) + write_output( + output_path, + prefix, + final_bins, + contigs_file, + contigs_map.inverse, + bins, + contigs_map, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, + ) # Exit program # -------------- diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index 1aab35b..cf24878 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -11,22 +11,17 @@ graphbin_Flye.py makes use of the assembly graphs produced by Flye long read assembler. """ -import csv import logging -import os -import subprocess -import sys import time -from collections import defaultdict - -from cogent3.parse.fasta import MinimalFastaParser -from igraph import * - -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap -from graphbin.utils.graphbin_Func import getClosestLabelledVertices +from graphbin.utils.graphbin_Func import graphbin_main from graphbin.utils.graphbin_Options import PARSER -from graphbin.utils.labelpropagation.labelprop import LabelProp +from graphbin.utils.parsers import get_initial_bin_count +from graphbin.utils.parsers.flye_parser import ( + parse_graph, + get_initial_binning_result, + write_output, +) __author__ = "Vijini Mallawaarachchi" @@ -42,6 +37,7 @@ def run(args): # Setup logger # ----------------------- + logger = logging.getLogger("GraphBin %s" % __version__) logger.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") @@ -61,7 +57,6 @@ def run(args): delimiter = args.delimiter max_iteration = args.max_iteration diff_threshold = args.diff_threshold - MIN_BIN_COUNT = 10 # Setup output path for log file # --------------------------------------------------- @@ -90,605 +85,59 @@ def run(args): # Get the number of bins from the initial binning result # -------------------------------------------------------- - n_bins = 0 - - try: - all_bins_list = [] - - with open(contig_bins_file) as csvfile: - readCSV = csv.reader(csvfile, delimiter=delimiter) - for row in readCSV: - all_bins_list.append(row[1]) - - bins_list = list(set(all_bins_list)) - bins_list.sort() - - n_bins = len(bins_list) - logger.info("Number of bins available in the binning result: " + str(n_bins)) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the binning result file is provided and it is having the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Constructing the assembly graph") - - # Get contig names - # ----------------------------------- - - contig_names = BidirectionalMap() - - contig_num = 0 - - with open(contig_paths, "r") as file: - - for line in file.readlines(): - - if not line.startswith("#"): - name = line.strip().split()[0] - contig_names[contig_num] = name - contig_num += 1 - - contig_names_rev = contig_names.inverse - - # Get the paths and edges - # ----------------------------------- - - paths = {} - segment_contigs = {} - - try: - - with open(contig_paths) as file: - - for line in file.readlines(): - - if not line.startswith("#"): - - strings = line.strip().split() - - contig_name = strings[0] - - path = strings[-1] - path = path.replace("*", "") - - if path.startswith(","): - path = path[1:] - - if path.endswith(","): - path = path[:-1] - - segments = path.rstrip().split(",") - - contig_num = contig_names_rev[contig_name] - - if contig_num not in paths: - paths[contig_num] = segments - - for segment in segments: - - if segment != "": - - if segment not in segment_contigs: - segment_contigs[segment] = set([contig_num]) - else: - segment_contigs[segment].add(contig_num) - - links_map = defaultdict(set) - - # Get links from assembly_graph.gfa - with open(assembly_graph_file) as file: - - for line in file.readlines(): - line = line.strip() - - # Identify lines with link information - if line.startswith("L"): - strings = line.split("\t") - - f1, f2 = "", "" - - if strings[2] == "+": - f1 = strings[1][5:] - if strings[2] == "-": - f1 = "-" + strings[1][5:] - if strings[4] == "+": - f2 = strings[3][5:] - if strings[4] == "-": - f2 = "-" + strings[3][5:] - - links_map[f1].add(f2) - links_map[f2].add(f1) - - # Create list of edges - edge_list = [] - - for i in paths: - segments = paths[i] - - new_links = [] - - for segment in segments: - - my_segment = segment - my_segment_num = "" + n_bins, bins_list = get_initial_bin_count(contig_bins_file, delimiter) - my_segment_rev = "" + # Get assembly graph + # -------------------- - if my_segment.startswith("-"): - my_segment_rev = my_segment[1:] - my_segment_num = my_segment[1:] - else: - my_segment_rev = "-" + my_segment - my_segment_num = my_segment - - if my_segment in links_map: - new_links.extend(list(links_map[my_segment])) - - if my_segment_rev in links_map: - new_links.extend(list(links_map[my_segment_rev])) - - if my_segment in segment_contigs: - for contig in segment_contigs[my_segment]: - if i != contig: - # Add edge to list of edges - edge_list.append((i, contig)) - - if my_segment_rev in segment_contigs: - for contig in segment_contigs[my_segment_rev]: - if i != contig: - # Add edge to list of edges - edge_list.append((i, contig)) - - if my_segment_num in segment_contigs: - for contig in segment_contigs[my_segment_num]: - if i != contig: - # Add edge to list of edges - edge_list.append((i, contig)) - - for new_link in new_links: - if new_link in segment_contigs: - for contig in segment_contigs[new_link]: - if i != contig: - # Add edge to list of edges - edge_list.append((i, contig)) - - if new_link.startswith("-"): - if new_link[1:] in segment_contigs: - for contig in segment_contigs[new_link[1:]]: - if i != contig: - # Add edge to list of edges - edge_list.append((i, contig)) - - node_count = len(contig_names_rev) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the assembly graph file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Total number of contigs available: " + str(node_count)) - - ## Construct the assembly graph - # ------------------------------- - - try: - - # Create the graph - assembly_graph = Graph() - - # Add vertices - assembly_graph.add_vertices(node_count) - - # Name vertices - for i in range(len(assembly_graph.vs)): - assembly_graph.vs[i]["id"] = i - assembly_graph.vs[i]["label"] = str(contig_names[i]) - - # Add edges to the graph - assembly_graph.add_edges(edge_list) - assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the assembly graph file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + assembly_graph, contig_names, node_count = parse_graph( + assembly_graph_file, contig_paths + ) # Get initial binning result # ---------------------------- - logger.info("Obtaining the initial binning result") - - bins = [[] for x in range(n_bins)] - - try: - with open(contig_bins_file) as contig_bins: - readCSV = csv.reader(contig_bins, delimiter=delimiter) - for row in readCSV: - contig_num = contig_names_rev[row[0]] - - bin_num = bins_list.index(row[1]) - bins[bin_num].append(contig_num) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Remove labels of ambiguous vertices - # ------------------------------------- - - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - neighbours_have_same_label_list = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - # Get set of closest labelled vertices with distance = 1 - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - neighbours_binned = False - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - neighbours_binned = True - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - elif neighbours_binned: - neighbours_have_same_label_list.append(i) - - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - # Further remove labels of ambiguous vertices - binned_contigs = [] - - for n in range(n_bins): - binned_contigs = sorted(binned_contigs + bins[n]) - - for b in range(n_bins): - - for i in bins[b]: - - if i not in neighbours_have_same_label_list: - - my_bin = b - - # Get set of closest labelled vertices - closest_neighbours = getClosestLabelledVertices( - assembly_graph, i, binned_contigs - ) - - if len(closest_neighbours) > 0: - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label and i not in remove_labels: - if my_bin in remove_by_bin: - if ( - len(bins[my_bin]) - len(remove_by_bin[my_bin]) - >= MIN_BIN_COUNT - ): - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - logger.info("Obtaining refined binning result") - - # Get vertices which are not isolated and not in components without any labels - # ----------------------------------------------------------------------------- - - logger.info( - "Deteremining vertices which are not isolated and not in components without any labels" - ) - - non_isolated = [] - - for i in range(node_count): - - if i not in non_isolated and i in binned_contigs: - - component = [] - component.append(i) - length = len(component) - neighbours = assembly_graph.neighbors(i, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - component = list(set(component)) - - while length != len(component): - - length = len(component) - - for j in component: - - neighbours = assembly_graph.neighbors(j, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - labelled = False - for j in component: - if j in binned_contigs: - labelled = True - break - - if labelled: - for j in component: - if j not in non_isolated: - non_isolated.append(j) - - logger.info("Number of non-isolated contigs: " + str(len(non_isolated))) - - # Run label propagation - # ----------------------- - - data = [] - - for contig in range(node_count): - - # Consider vertices that are not isolated - - if contig in non_isolated: - line = [] - line.append(contig) - - assigned = False - - for i in range(n_bins): - if contig in bins[i]: - line.append(i + 1) - assigned = True - - if not assigned: - line.append(0) - - neighbours = assembly_graph.neighbors(contig, mode=ALL) - - neighs = [] - - for neighbour in neighbours: - n = [] - n.append(neighbour) - n.append(1.0) - neighs.append(n) - - line.append(neighs) - - data.append(line) - - # Check if initial binning result consists of contigs belonging to multiple bins - - multiple_bins = False - - for item in data: - if type(item[1]) is int and type(item[2]) is int: - multiple_bins = True - break - - if multiple_bins: - logger.error( - "Initial binning result consists of contigs belonging to multiple bins. Please make sure that each contig in the initial binning result belongs to only one bin." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Label propagation - - lp = LabelProp() - - lp.load_data_from_mem(data) - - logger.info( - "Starting label propagation with eps=" - + str(diff_threshold) - + " and max_iteration=" - + str(max_iteration) + bins = get_initial_binning_result( + n_bins, bins_list, contig_bins_file, contig_names.inverse, delimiter ) - ans = lp.run(diff_threshold, max_iteration, show_log=True, clean_result=False) - - logger.info("Obtaining Label Propagation result") - - for l in ans: - for i in range(n_bins): - if l[1] == i + 1 and l[0] not in bins[i]: - bins[i].append(l[0]) - - # Remove labels of ambiguous vertices + # Run GraphBin logic # ------------------------------------- - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) + final_bins, remove_labels, non_isolated = graphbin_main( + n_bins, + bins, + bins_list, + assembly_graph, + node_count, + diff_threshold, + max_iteration, + ) elapsed_time = time.time() - start_time - logger.info("Obtaining the Final Refined Binning result") - - final_bins = {} - - for i in range(n_bins): - for contig in bins[i]: - final_bins[contig] = bins_list[i] - # Print elapsed time for the process logger.info("Elapsed time: " + str(elapsed_time) + " seconds") # Write result to output file # ----------------------------- + write_output( + output_path, + prefix, + final_bins, + contigs_file, + contig_names.inverse, + bins, + contig_names, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, + ) logger.info("Writing the Final Binning result to file") - output_bins = [] - - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" - - if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) - - bin_files = {} - - for bin_name in set(final_bins.values()): - bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" - ) - - for label, seq in MinimalFastaParser(contigs_file): - - contig_num = contig_names_rev[label] - - if contig_num in final_bins: - bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") - - # Close output files - for c in set(final_bins.values()): - bin_files[c].close() - - for b in range(len(bins)): - - # with open(output_bins_path + "bin_" + str(b+1) + "_ids.txt", "w") as bin_file: - for contig in bins[b]: - line = [] - line.append(str(contig_names[contig])) - line.append(bins_list[b]) - output_bins.append(line) - # bin_file.write(str(contig_names[contig])+"\n") - - # subprocess.run("awk -F'>' 'NR==FNR{ids[$0]; next} NF>1{f=($2 in ids)} f' " + output_bins_path + "bin_" + str(b+1) + "_ids.txt " + contigs_file + " > " + output_bins_path + "bin_" +str(b+1) +"_seqs.fasta", shell=True) - - with open(output_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - for row in output_bins: - output_writer.writerow(row) - - logger.info("Final binning results can be found in " + str(output_bins_path)) - - unbinned_contigs = [] - - for i in range(node_count): - if i in remove_labels or i not in non_isolated: - line = [] - line.append(str(contig_names[i])) - unbinned_contigs.append(line) - - if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" - - with open(unbinned_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - - for row in unbinned_contigs: - output_writer.writerow(row) - - logger.info("Unbinned contigs can be found at " + unbinned_file) - # Exit program # -------------- diff --git a/src/graphbin/utils/graphbin_Func.py b/src/graphbin/utils/graphbin_Func.py index 3e3c01e..468a4ca 100644 --- a/src/graphbin/utils/graphbin_Func.py +++ b/src/graphbin/utils/graphbin_Func.py @@ -1,5 +1,10 @@ #!/usr/bin/env python3 +import logging +import sys + +from graphbin.utils.labelpropagation.labelprop import LabelProp + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] @@ -9,6 +14,10 @@ __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" +logger = logging.getLogger("GraphBin %s" % __version__) + +MIN_BIN_COUNT = 10 + def getClosestLabelledVertices(graph, node, binned_contigs): # Remove labels of ambiguous vertices @@ -42,3 +51,275 @@ def getClosestLabelledVertices(graph, node, binned_contigs): if len(temp2) > 0: queu_l.append(temp2) return labelled + + +def graphbin_main( + n_bins, bins, bins_list, assembly_graph, node_count, diff_threshold, max_iteration +): + logger.info("Determining ambiguous vertices") + + remove_by_bin = {} + + remove_labels = [] + + neighbours_have_same_label_list = [] + + for b in range(n_bins): + for i in bins[b]: + my_bin = b + + # Get set of closest labelled vertices with distance = 1 + closest_neighbours = assembly_graph.neighbors(i, mode="all") + + # Determine whether all the closest labelled vertices have the same label as its own + neighbours_have_same_label = True + + neighbours_binned = False + + for neighbour in closest_neighbours: + for k in range(n_bins): + if neighbour in bins[k]: + neighbours_binned = True + if k != my_bin: + neighbours_have_same_label = False + break + + if not neighbours_have_same_label: + if my_bin in remove_by_bin: + if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: + remove_labels.append(i) + remove_by_bin[my_bin].append(i) + else: + if len(bins[my_bin]) >= MIN_BIN_COUNT: + remove_labels.append(i) + remove_by_bin[my_bin] = [i] + + elif neighbours_binned: + neighbours_have_same_label_list.append(i) + + for i in remove_labels: + for n in range(n_bins): + if i in bins[n]: + bins[n].remove(i) + + # Further remove labels of ambiguous vertices + binned_contigs = [] + + for n in range(n_bins): + binned_contigs = sorted(binned_contigs + bins[n]) + + for b in range(n_bins): + for i in bins[b]: + if i not in neighbours_have_same_label_list: + my_bin = b + + # Get set of closest labelled vertices + closest_neighbours = getClosestLabelledVertices( + assembly_graph, i, binned_contigs + ) + + if len(closest_neighbours) > 0: + # Determine whether all the closest labelled vertices have the same label as its own + neighbours_have_same_label = True + + for neighbour in closest_neighbours: + for k in range(n_bins): + if neighbour in bins[k]: + if k != my_bin: + neighbours_have_same_label = False + break + + if not neighbours_have_same_label and i not in remove_labels: + if my_bin in remove_by_bin: + if ( + len(bins[my_bin]) - len(remove_by_bin[my_bin]) + >= MIN_BIN_COUNT + ): + remove_labels.append(i) + remove_by_bin[my_bin].append(i) + else: + if len(bins[my_bin]) >= MIN_BIN_COUNT: + remove_labels.append(i) + remove_by_bin[my_bin] = [i] + + logger.info("Removing labels of ambiguous vertices") + + # Remove labels of ambiguous vertices + for i in remove_labels: + for n in range(n_bins): + if i in bins[n]: + bins[n].remove(i) + + logger.info("Obtaining the refined binning result") + + # Get vertices which are not isolated and not in components without any labels + # ----------------------------------------------------------------------------- + + logger.info( + "Deteremining vertices which are not isolated and not in components without any labels" + ) + + non_isolated = [] + + for i in range(node_count): + if i not in non_isolated and i in binned_contigs: + component = [] + component.append(i) + length = len(component) + neighbours = assembly_graph.neighbors(i, mode="all") + + for neighbor in neighbours: + if neighbor not in component: + component.append(neighbor) + + component = list(set(component)) + + while length != len(component): + length = len(component) + + for j in component: + neighbours = assembly_graph.neighbors(j, mode="all") + + for neighbor in neighbours: + if neighbor not in component: + component.append(neighbor) + + labelled = False + for j in component: + if j in binned_contigs: + labelled = True + break + + if labelled: + for j in component: + if j not in non_isolated: + non_isolated.append(j) + + logger.info("Number of non-isolated contigs: " + str(len(non_isolated))) + + # Run label propagation + # ----------------------- + + data = [] + + for contig in range(node_count): + # Consider vertices that are not isolated + + if contig in non_isolated: + line = [] + line.append(contig) + + assigned = False + + for i in range(n_bins): + if contig in bins[i]: + line.append(i + 1) + assigned = True + + if not assigned: + line.append(0) + + neighbours = assembly_graph.neighbors(contig, mode="all") + + neighs = [] + + for neighbour in neighbours: + n = [] + n.append(neighbour) + n.append(1.0) + neighs.append(n) + + line.append(neighs) + + data.append(line) + + # Check if initial binning result consists of contigs belonging to multiple bins + + multiple_bins = False + + for item in data: + if type(item[1]) is int and type(item[2]) is int: + multiple_bins = True + break + + if multiple_bins: + logger.error( + "Initial binning result consists of contigs belonging to multiple bins. Please make sure that each contig in the initial binning result belongs to only one bin." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + # Label propagation + + lp = LabelProp() + + lp.load_data_from_mem(data) + + logger.info( + "Starting label propagation with eps=" + + str(diff_threshold) + + " and max_iteration=" + + str(max_iteration) + ) + + ans = lp.run(diff_threshold, max_iteration, show_log=True, clean_result=False) + + logger.info("Obtaining Label Propagation result") + + for l in ans: + for i in range(n_bins): + if l[1] == i + 1 and l[0] not in bins[i]: + bins[i].append(l[0]) + + # Remove labels of ambiguous vertices + # ------------------------------------- + + logger.info("Determining ambiguous vertices") + + remove_by_bin = {} + + remove_labels = [] + + for b in range(n_bins): + for i in bins[b]: + my_bin = b + + closest_neighbours = assembly_graph.neighbors(i, mode="all") + + # Determine whether all the closest labelled vertices have the same label as its own + neighbours_have_same_label = True + + for neighbour in closest_neighbours: + for k in range(n_bins): + if neighbour in bins[k]: + if k != my_bin: + neighbours_have_same_label = False + break + + if not neighbours_have_same_label: + if my_bin in remove_by_bin: + if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: + remove_labels.append(i) + remove_by_bin[my_bin].append(i) + else: + if len(bins[my_bin]) >= MIN_BIN_COUNT: + remove_labels.append(i) + remove_by_bin[my_bin] = [i] + + logger.info("Removing labels of ambiguous vertices") + + # Remove labels of ambiguous vertices + for i in remove_labels: + for n in range(n_bins): + if i in bins[n]: + bins[n].remove(i) + + logger.info("Obtaining the Final Refined Binning result") + + final_bins = {} + + for i in range(n_bins): + for contig in bins[i]: + final_bins[contig] = bins_list[i] + + return final_bins, remove_labels, non_isolated diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/utils/graphbin_MEGAHIT.py index 98b8e57..374c965 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/utils/graphbin_MEGAHIT.py @@ -11,21 +11,18 @@ graphbin_MEGAHIT.py makes use of the assembly graphs produced by MEGAHIT assembler. """ -import csv import logging -import os -import re -import subprocess -import sys import time -from cogent3.parse.fasta import MinimalFastaParser -from igraph import * - -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap -from graphbin.utils.graphbin_Func import getClosestLabelledVertices +from graphbin.utils.graphbin_Func import graphbin_main from graphbin.utils.graphbin_Options import PARSER -from graphbin.utils.labelpropagation.labelprop import LabelProp +from graphbin.utils.parsers import get_initial_bin_count +from graphbin.utils.parsers.megahit_parser import ( + parse_graph, + get_initial_binning_result, + get_contig_descriptors, + write_output, +) __author__ = "Vijini Mallawaarachchi" @@ -88,534 +85,67 @@ def run(args): # Get the number of bins from the initial binning result # -------------------------------------------------------- - n_bins = 0 - - try: - all_bins_list = [] - - with open(contig_bins_file) as csvfile: - readCSV = csv.reader(csvfile, delimiter=delimiter) - for row in readCSV: - all_bins_list.append(row[1]) - - bins_list = list(set(all_bins_list)) - bins_list.sort() - - n_bins = len(bins_list) - logger.info( - "Number of bins available in the initial binning result: " + str(n_bins) - ) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the initial binning result file is provided and it is having the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Constructing the assembly graph") + n_bins, bins_list = get_initial_bin_count(contig_bins_file, delimiter) # Get original contig IDs # ------------------------------- - original_contigs = {} - contig_descriptions = {} - for label, seq in MinimalFastaParser(contigs_file): - name = label.split()[0] - original_contigs[name] = seq - contig_descriptions[name] = label - - # Construct the assembly graph - # ------------------------------- - - node_count = 0 - - graph_contigs = {} - - links = [] - - my_map = BidirectionalMap() - - try: - - # Get links from .gfa file - with open(assembly_graph_file) as file: - - for line in file.readlines(): - line = line.strip() - - # Identify lines with link information - if line.startswith("L"): - link = [] - - strings = line.split("\t") - - start_1 = "NODE_" - end_1 = "_length" - - link1 = int( - re.search("%s(.*)%s" % (start_1, end_1), strings[1]).group(1) - ) - - start_2 = "NODE_" - end_2 = "_length" - - link2 = int( - re.search("%s(.*)%s" % (start_2, end_2), strings[3]).group(1) - ) - - link.append(link1) - link.append(link2) - links.append(link) - - elif line.startswith("S"): - strings = line.split() - - start = "NODE_" - end = "_length" - - contig_num = int( - re.search("%s(.*)%s" % (start, end), strings[1]).group(1) - ) - - my_map[node_count] = int(contig_num) - - graph_contigs[contig_num] = strings[2] - - node_count += 1 - - logger.info("Total number of contigs available: " + str(node_count)) - - contigs_map = my_map - contigs_map_rev = my_map.inverse - - # Create graph - assembly_graph = Graph() - - # Add vertices - assembly_graph.add_vertices(node_count) - - # Create list of edges - edge_list = [] - - for i in range(node_count): - assembly_graph.vs[i]["id"] = i - assembly_graph.vs[i]["label"] = str(contigs_map[i]) - - # Iterate links - for link in links: - # Remove self loops - if link[0] != link[1]: - # Add edge to list of edges - edge_list.append((contigs_map_rev[link[0]], contigs_map_rev[link[1]])) - - # Add edges to the graph - assembly_graph.add_edges(edge_list) - assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the assembly graph file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + original_contigs = get_contig_descriptors(contigs_file) - # Map original contig IDs to contig IDS of assembly graph - # -------------------------------------------------------- - - graph_to_contig_map = BidirectionalMap() - - for (n, m), (n2, m2) in zip(graph_contigs.items(), original_contigs.items()): - if m == m2: - graph_to_contig_map[n] = n2 + # Get assembly graph + # -------------------- - graph_to_contig_map_rev = graph_to_contig_map.inverse + assembly_graph, graph_to_contig_map, contigs_map, node_count = parse_graph( + assembly_graph_file, original_contigs + ) # Get initial binning result # ---------------------------- - logger.info("Obtaining the initial binning result") - - bins = [[] for x in range(n_bins)] - - try: - with open(contig_bins_file) as contig_bins: - readCSV = csv.reader(contig_bins, delimiter=delimiter) - for row in readCSV: - contig_num = contigs_map_rev[int(graph_to_contig_map_rev[row[0]])] - - bin_num = bins_list.index(row[1]) - bins[bin_num].append(contig_num) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Remove labels of ambiguous vertices - # ------------------------------------- - - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - neighbours_have_same_label_list = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - # Get set of closest labelled vertices with distance = 1 - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - neighbours_binned = False - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - neighbours_binned = True - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - elif neighbours_binned: - neighbours_have_same_label_list.append(i) - - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - # Further remove labels of ambiguous vertices - binned_contigs = [] - - for n in range(n_bins): - binned_contigs = sorted(binned_contigs + bins[n]) - - for b in range(n_bins): - - for i in bins[b]: - - if i not in neighbours_have_same_label_list: - - my_bin = b - - # Get set of closest labelled vertices - closest_neighbours = getClosestLabelledVertices( - assembly_graph, i, binned_contigs - ) - - if len(closest_neighbours) > 0: - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label and i not in remove_labels: - if my_bin in remove_by_bin: - if ( - len(bins[my_bin]) - len(remove_by_bin[my_bin]) - >= MIN_BIN_COUNT - ): - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - logger.info("Obtaining refined binning result") - - # Get vertices which are not isolated and not in components without any labels - # ----------------------------------------------------------------------------- - - logger.info( - "Deteremining vertices which are not isolated and not in components without any labels" - ) - - non_isolated = [] - - for i in range(node_count): - - if i not in non_isolated and i in binned_contigs: - - component = [] - component.append(i) - length = len(component) - neighbours = assembly_graph.neighbors(i, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - component = list(set(component)) - - while length != len(component): - - length = len(component) - - for j in component: - - neighbours = assembly_graph.neighbors(j, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - labelled = False - for j in component: - if j in binned_contigs: - labelled = True - break - - if labelled: - for j in component: - if j not in non_isolated: - non_isolated.append(j) - - logger.info("Number of non-isolated contigs: " + str(len(non_isolated))) - - # Run label propagation - # ----------------------- - - data = [] - - for contig in range(node_count): - - # Consider vertices that are not isolated - - if contig in non_isolated: - line = [] - line.append(contig) - - assigned = False - - for i in range(n_bins): - if contig in bins[i]: - line.append(i + 1) - assigned = True - - if not assigned: - line.append(0) - - neighbours = assembly_graph.neighbors(contig, mode=ALL) - - neighs = [] - - for neighbour in neighbours: - n = [] - n.append(neighbour) - n.append(1.0) - neighs.append(n) - - line.append(neighs) - - data.append(line) - - # Check if initial binning result consists of contigs belonging to multiple bins - - multiple_bins = False - - for item in data: - if type(item[1]) is int and type(item[2]) is int: - multiple_bins = True - break - - if multiple_bins: - logger.error( - "Initial binning result consists of contigs belonging to multiple bins. Please make sure that each contig in the initial binning result belongs to only one bin." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Label propagation - - lp = LabelProp() - - lp.load_data_from_mem(data) - - logger.info( - "Starting label propagation with eps=" - + str(diff_threshold) - + " and max_iteration=" - + str(max_iteration) + bins = get_initial_binning_result( + n_bins, + bins_list, + contig_bins_file, + contigs_map.inverse, + graph_to_contig_map.inverse, + delimiter, ) - ans = lp.run(diff_threshold, max_iteration, show_log=True, clean_result=False) - - logger.info("Obtaining Label Propagation result") - - for l in ans: - for i in range(n_bins): - if l[1] == i + 1 and l[0] not in bins[i]: - bins[i].append(l[0]) - - # Remove labels of ambiguous vertices + # Run GraphBin logic # ------------------------------------- - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) + final_bins, remove_labels, non_isolated = graphbin_main( + n_bins, + bins, + bins_list, + assembly_graph, + node_count, + diff_threshold, + max_iteration, + ) elapsed_time = time.time() - start_time - logger.info("Obtaining the Final Refined Binning result") - - final_bins = {} - - for i in range(n_bins): - for contig in bins[i]: - final_bins[contig] = bins_list[i] - # Print elapsed time for the process logger.info("Elapsed time: " + str(elapsed_time) + " seconds") # Write result to output file # ----------------------------- - logger.info("Writing the Final Binning result to file") - - output_bins = [] - - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" - - if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) - - bin_files = {} - - for bin_name in set(final_bins.values()): - bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" - ) - - for label, seq in MinimalFastaParser( - contigs_file, label_to_name=lambda x: x.split()[0] - ): - - contig_num = contigs_map_rev[graph_to_contig_map_rev[label]] - - if contig_num in final_bins: - bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") - - # Close output files - for c in set(final_bins.values()): - bin_files[c].close() - - for b in range(len(bins)): - - for contig in bins[b]: - line = [] - line.append(graph_to_contig_map[contigs_map[contig]]) - line.append(bins_list[b]) - output_bins.append(line) - - with open(output_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - for row in output_bins: - output_writer.writerow(row) - - logger.info("Final binning results can be found in " + str(output_bins_path)) - - unbinned_contigs = [] - - for i in range(node_count): - if i in remove_labels or i not in non_isolated: - line = [] - line.append(graph_to_contig_map[contigs_map[i]]) - unbinned_contigs.append(line) - - if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" - - with open(unbinned_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - - for row in unbinned_contigs: - output_writer.writerow(row) - - logger.info("Unbinned contigs can be found at " + unbinned_file) + write_output( + output_path, + prefix, + final_bins, + contigs_file, + graph_to_contig_map, + bins, + contigs_map, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, + ) # Exit program # -------------- diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/utils/graphbin_Miniasm.py index 0a31d13..1e69b26 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/utils/graphbin_Miniasm.py @@ -11,20 +11,17 @@ graphbin_Miniasm.py makes use of the assembly graphs produced by Miniasm long read assembler. """ -import csv import logging -import os -import subprocess -import sys import time -from cogent3.parse.fasta import MinimalFastaParser -from igraph import * - -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap -from graphbin.utils.graphbin_Func import getClosestLabelledVertices +from graphbin.utils.graphbin_Func import graphbin_main from graphbin.utils.graphbin_Options import PARSER -from graphbin.utils.labelpropagation.labelprop import LabelProp +from graphbin.utils.parsers import get_initial_bin_count +from graphbin.utils.parsers.miniasm_parser import ( + parse_graph, + get_initial_binning_result, + write_output, +) __author__ = "Vijini Mallawaarachchi" @@ -87,503 +84,54 @@ def run(args): # Get the number of bins from the initial binning result # -------------------------------------------------------- - n_bins = 0 - - try: - all_bins_list = [] - - with open(contig_bins_file) as csvfile: - readCSV = csv.reader(csvfile, delimiter=delimiter) - for row in readCSV: - all_bins_list.append(row[1]) - - bins_list = list(set(all_bins_list)) - bins_list.sort() - - n_bins = len(bins_list) - logger.info("Number of bins available in the binning result: " + str(n_bins)) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the binning result file is provided and it is having the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Constructing the assembly graph") - - # Get the links from the .gfa file - # ----------------------------------- - - my_map = BidirectionalMap() - - node_count = 0 - - nodes = [] - - links = [] - - try: - # Get contig connections from .gfa file - with open(assembly_graph_file) as file: - - for line in file.readlines(): - line = line.strip() - - # Count the number of contigs - if line.startswith("S"): - strings = line.split("\t") - my_node = strings[1] - my_map[node_count] = my_node - nodes.append(my_node) - node_count += 1 - - # Identify lines with link information - elif line.startswith("L"): - - link = [] - strings = line.split("\t") - - if strings[1] != strings[3]: - start = strings[1] - end = strings[3] - link.append(start) - link.append(end) - links.append(link) + n_bins, bins_list = get_initial_bin_count(contig_bins_file, delimiter) - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the assembly graph file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) + # Get assembly graph + # -------------------- - contigs_map = my_map - contigs_map_rev = my_map.inverse - - logger.info("Total number of contigs available: " + str(node_count)) - - ## Construct the assembly graph - # ------------------------------- - - try: - - # Create the graph - assembly_graph = Graph() - - # Create list of edges - edge_list = [] - - # Add vertices - assembly_graph.add_vertices(node_count) - - # Name vertices - for i in range(len(assembly_graph.vs)): - assembly_graph.vs[i]["id"] = i - assembly_graph.vs[i]["label"] = str(contigs_map[i]) - - # Iterate links - for link in links: - # Remove self loops - if link[0] != link[1]: - # Add edge to list of edges - edge_list.append((contigs_map_rev[link[0]], contigs_map_rev[link[1]])) - - # Add edges to the graph - assembly_graph.add_edges(edge_list) - assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the assembly graph file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + assembly_graph, contigs_map, node_count = parse_graph(assembly_graph_file) # Get initial binning result # ---------------------------- - logger.info("Obtaining the initial binning result") - - bins = [[] for x in range(n_bins)] - - try: - with open(contig_bins_file) as contig_bins: - readCSV = csv.reader(contig_bins, delimiter=delimiter) - for row in readCSV: - contig_num = contigs_map_rev[row[0]] - - bin_num = bins_list.index(row[1]) - bins[bin_num].append(contig_num) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Remove labels of ambiguous vertices - # ------------------------------------- - - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - neighbours_have_same_label_list = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - # Get set of closest labelled vertices with distance = 1 - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - neighbours_binned = False - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - neighbours_binned = True - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - elif neighbours_binned: - neighbours_have_same_label_list.append(i) - - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - # Further remove labels of ambiguous vertices - binned_contigs = [] - - for n in range(n_bins): - binned_contigs = sorted(binned_contigs + bins[n]) - - for b in range(n_bins): - - for i in bins[b]: - - if i not in neighbours_have_same_label_list: - - my_bin = b - - # Get set of closest labelled vertices - closest_neighbours = getClosestLabelledVertices( - assembly_graph, i, binned_contigs - ) - - if len(closest_neighbours) > 0: - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label and i not in remove_labels: - if my_bin in remove_by_bin: - if ( - len(bins[my_bin]) - len(remove_by_bin[my_bin]) - >= MIN_BIN_COUNT - ): - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - logger.info("Obtaining refined binning result") - - # Get vertices which are not isolated and not in components without any labels - # ----------------------------------------------------------------------------- - - logger.info( - "Deteremining vertices which are not isolated and not in components without any labels" - ) - - non_isolated = [] - - for i in range(node_count): - - if i not in non_isolated and i in binned_contigs: - - component = [] - component.append(i) - length = len(component) - neighbours = assembly_graph.neighbors(i, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - component = list(set(component)) - - while length != len(component): - - length = len(component) - - for j in component: - - neighbours = assembly_graph.neighbors(j, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - labelled = False - for j in component: - if j in binned_contigs: - labelled = True - break - - if labelled: - for j in component: - if j not in non_isolated: - non_isolated.append(j) - - logger.info("Number of non-isolated contigs: " + str(len(non_isolated))) - - # Run label propagation - # ----------------------- - - data = [] - - for contig in range(node_count): - - # Consider vertices that are not isolated - - if contig in non_isolated: - line = [] - line.append(contig) - - assigned = False - - for i in range(n_bins): - if contig in bins[i]: - line.append(i + 1) - assigned = True - - if not assigned: - line.append(0) - - neighbours = assembly_graph.neighbors(contig, mode=ALL) - - neighs = [] - - for neighbour in neighbours: - n = [] - n.append(neighbour) - n.append(1.0) - neighs.append(n) - - line.append(neighs) - - data.append(line) - - # Check if initial binning result consists of contigs belonging to multiple bins - - multiple_bins = False - - for item in data: - if type(item[1]) is int and type(item[2]) is int: - multiple_bins = True - break - - if multiple_bins: - logger.error( - "Initial binning result consists of contigs belonging to multiple bins. Please make sure that each contig in the initial binning result belongs to only one bin." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Label propagation - - lp = LabelProp() - - lp.load_data_from_mem(data) - - logger.info( - "Starting label propagation with eps=" - + str(diff_threshold) - + " and max_iteration=" - + str(max_iteration) + bins = get_initial_binning_result( + n_bins, bins_list, contig_bins_file, contigs_map.inverse, delimiter ) - ans = lp.run(diff_threshold, max_iteration, show_log=True, clean_result=False) - - logger.info("Obtaining Label Propagation result") - - for l in ans: - for i in range(n_bins): - if l[1] == i + 1 and l[0] not in bins[i]: - bins[i].append(l[0]) - - # Remove labels of ambiguous vertices + # Run GraphBin logic # ------------------------------------- - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) + final_bins, remove_labels, non_isolated = graphbin_main( + n_bins, + bins, + bins_list, + assembly_graph, + node_count, + diff_threshold, + max_iteration, + ) elapsed_time = time.time() - start_time - logger.info("Obtaining the Final Refined Binning result") - - final_bins = {} - - for i in range(n_bins): - for contig in bins[i]: - final_bins[contig] = bins_list[i] - # Print elapsed time for the process logger.info("Elapsed time: " + str(elapsed_time) + " seconds") # Write result to output file # ----------------------------- - logger.info("Writing the Final Binning result to file") - - output_bins = [] - - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" - - if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) - - bin_files = {} - - for bin_name in set(final_bins.values()): - bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" - ) - - for label, seq in MinimalFastaParser(contigs_file): - - contig_num = contigs_map_rev[label] - - if contig_num in final_bins: - bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") - - # Close output files - for c in set(final_bins.values()): - bin_files[c].close() - - for b in range(len(bins)): - - for contig in bins[b]: - line = [] - line.append(str(contigs_map[contig])) - line.append(bins_list[b]) - output_bins.append(line) - - with open(output_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - for row in output_bins: - output_writer.writerow(row) - - logger.info("Final binning results can be found in " + str(output_bins_path)) - - unbinned_contigs = [] - - for i in range(node_count): - if i in remove_labels or i not in non_isolated: - line = [] - line.append(str(contigs_map[i])) - unbinned_contigs.append(line) - - if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" - - with open(unbinned_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - - for row in unbinned_contigs: - output_writer.writerow(row) - - logger.info("Unbinned contigs can be found at " + unbinned_file) + write_output( + output_path, + prefix, + final_bins, + contigs_file, + contigs_map, + bins, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, + ) # Exit program # -------------- diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/utils/graphbin_SGA.py index 44343dd..7e7670f 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/utils/graphbin_SGA.py @@ -11,21 +11,18 @@ graphbin_SGA.py makes use of the assembly graphs produced by SGA (String Graph Assembler). """ -import csv import logging -import os -import re -import subprocess -import sys import time -from cogent3.parse.fasta import MinimalFastaParser -from igraph import * - -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap -from graphbin.utils.graphbin_Func import getClosestLabelledVertices +from graphbin.utils.graphbin_Func import graphbin_main from graphbin.utils.graphbin_Options import PARSER -from graphbin.utils.labelpropagation.labelprop import LabelProp +from graphbin.utils.parsers import get_initial_bin_count +from graphbin.utils.parsers.sga_parser import ( + parse_graph, + get_initial_binning_result, + get_contig_descriptions, + write_output, +) __author__ = "Vijini Mallawaarachchi" @@ -60,7 +57,6 @@ def run(args): delimiter = args.delimiter max_iteration = args.max_iteration diff_threshold = args.diff_threshold - MIN_BIN_COUNT = 10 # Setup output path for log file # --------------------------------------------------- @@ -88,516 +84,60 @@ def run(args): # Get the number of bins from the initial binning result # -------------------------------------------------------- - n_bins = 0 - - try: - all_bins_list = [] - - with open(contig_bins_file) as csvfile: - readCSV = csv.reader(csvfile, delimiter=delimiter) - for row in readCSV: - all_bins_list.append(row[1]) - - bins_list = list(set(all_bins_list)) - bins_list.sort() - - n_bins = len(bins_list) - logger.info("Number of bins available in the binning result: " + str(n_bins)) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the binning result file is provided and it is having the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Constructing the assembly graph") - - contig_descriptions = {} - - for label, _ in MinimalFastaParser(contigs_file): - name = label.split()[0] - contig_descriptions[name] = label - - # Get the links from the .asqg file - # ----------------------------------- - - links = [] - - contig_names = BidirectionalMap() - - my_map = BidirectionalMap() - - node_count = 0 - - try: - # Get contig connections from .asqg file - with open(assembly_graph_file) as file: - - for line in file.readlines(): - line = line.strip() - - # Count the number of contigs - if line.startswith("VT"): - start = "contig-" - end = "" - contig_name = str(line.split()[1]) - contig_num = int( - re.search("%s(.*)%s" % (start, end), contig_name).group(1) - ) - my_map[node_count] = contig_num - contig_names[node_count] = contig_name.strip() - node_count += 1 - - # Identify lines with link information - elif line.startswith("ED"): - link = [] - strings = line.split("\t")[1].split() - link.append(int(strings[0][7:])) - link.append(int(strings[1][7:])) - links.append(link) + n_bins, bins_list = get_initial_bin_count(contig_bins_file, delimiter) - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the assembly graph file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) + # Get assembly graph + # -------------------- - contigs_map = my_map - contigs_map_rev = my_map.inverse - - contig_names_rev = contig_names.inverse - - logger.info("Total number of contigs available: " + str(node_count)) - - ## Construct the assembly graph - # ------------------------------- - - try: - - # Create the graph - assembly_graph = Graph() - - # Create list of edges - edge_list = [] - - # Add vertices - assembly_graph.add_vertices(node_count) - - # Name vertices - for i in range(len(assembly_graph.vs)): - assembly_graph.vs[i]["id"] = i - assembly_graph.vs[i]["label"] = str(contigs_map[i]) - - # Iterate links - for link in links: - # Remove self loops - if link[0] != link[1]: - # Add edge to list of edges - edge_list.append((contigs_map_rev[link[0]], contigs_map_rev[link[1]])) - - # Add edges to the graph - assembly_graph.add_edges(edge_list) - assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the assembly graph file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + assembly_graph, contigs_map, contig_names, node_count = parse_graph( + assembly_graph_file + ) # Get initial binning result # ---------------------------- - logger.info("Obtaining the initial binning result") - - bins = [[] for x in range(n_bins)] - - try: - with open(contig_bins_file) as contig_bins: - readCSV = csv.reader(contig_bins, delimiter=delimiter) - for row in readCSV: - start = "contig-" - end = "" - contig_num = contigs_map_rev[ - int(re.search("%s(.*)%s" % (start, end), row[0]).group(1)) - ] - - bin_num = bins_list.index(row[1]) - bins[bin_num].append(contig_num) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Remove labels of ambiguous vertices - # ------------------------------------- - - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - neighbours_have_same_label_list = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - # Get set of closest labelled vertices with distance = 1 - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - neighbours_binned = False - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - neighbours_binned = True - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - elif neighbours_binned: - neighbours_have_same_label_list.append(i) - - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - # Further remove labels of ambiguous vertices - binned_contigs = [] - - for n in range(n_bins): - binned_contigs = sorted(binned_contigs + bins[n]) - - for b in range(n_bins): - - for i in bins[b]: - - if i not in neighbours_have_same_label_list: - - my_bin = b - - # Get set of closest labelled vertices - closest_neighbours = getClosestLabelledVertices( - assembly_graph, i, binned_contigs - ) - - if len(closest_neighbours) > 0: - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label and i not in remove_labels: - if my_bin in remove_by_bin: - if ( - len(bins[my_bin]) - len(remove_by_bin[my_bin]) - >= MIN_BIN_COUNT - ): - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - logger.info("Obtaining refined binning result") - - # Get vertices which are not isolated and not in components without any labels - # ----------------------------------------------------------------------------- - - logger.info( - "Deteremining vertices which are not isolated and not in components without any labels" - ) - - non_isolated = [] - - for i in range(node_count): - - if i not in non_isolated and i in binned_contigs: - - component = [] - component.append(i) - length = len(component) - neighbours = assembly_graph.neighbors(i, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - component = list(set(component)) - - while length != len(component): - - length = len(component) - - for j in component: - - neighbours = assembly_graph.neighbors(j, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - labelled = False - for j in component: - if j in binned_contigs: - labelled = True - break - - if labelled: - for j in component: - if j not in non_isolated: - non_isolated.append(j) - - logger.info("Number of non-isolated contigs: " + str(len(non_isolated))) - - # Run label propagation - # ----------------------- - - data = [] - - for contig in range(node_count): - - # Consider vertices that are not isolated - - if contig in non_isolated: - line = [] - line.append(contig) - - assigned = False - - for i in range(n_bins): - if contig in bins[i]: - line.append(i + 1) - assigned = True - - if not assigned: - line.append(0) - - neighbours = assembly_graph.neighbors(contig, mode=ALL) - - neighs = [] - - for neighbour in neighbours: - n = [] - n.append(neighbour) - n.append(1.0) - neighs.append(n) - - line.append(neighs) - - data.append(line) - - # Check if initial binning result consists of contigs belonging to multiple bins - - multiple_bins = False - - for item in data: - if type(item[1]) is int and type(item[2]) is int: - multiple_bins = True - break - - if multiple_bins: - logger.error( - "Initial binning result consists of contigs belonging to multiple bins. Please make sure that each contig in the initial binning result belongs to only one bin." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Label propagation - - lp = LabelProp() - - lp.load_data_from_mem(data) - - logger.info( - "Starting label propagation with eps=" - + str(diff_threshold) - + " and max_iteration=" - + str(max_iteration) + bins = get_initial_binning_result( + n_bins, bins_list, contig_bins_file, contigs_map.inverse, delimiter ) - ans = lp.run(diff_threshold, max_iteration, show_log=True, clean_result=False) - - logger.info("Obtaining Label Propagation result") - - for l in ans: - for i in range(n_bins): - if l[1] == i + 1 and l[0] not in bins[i]: - bins[i].append(l[0]) + contig_descriptions = get_contig_descriptions(contigs_file) - # Remove labels of ambiguous vertices + # Run GraphBin logic # ------------------------------------- - - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) + + final_bins, remove_labels, non_isolated = graphbin_main( + n_bins, + bins, + bins_list, + assembly_graph, + node_count, + diff_threshold, + max_iteration, + ) elapsed_time = time.time() - start_time - logger.info("Obtaining the Final Refined Binning result") - - final_bins = {} - - for i in range(n_bins): - for contig in bins[i]: - final_bins[contig] = bins_list[i] - # Print elapsed time for the process logger.info("Elapsed time: " + str(elapsed_time) + " seconds") # Write result to output file # ----------------------------- - logger.info("Writing the Final Binning result to file") - - output_bins = [] - - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" - - if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) - - bin_files = {} - - for bin_name in set(final_bins.values()): - bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" - ) - - for label, seq in MinimalFastaParser( - contigs_file, label_to_name=lambda x: x.split()[0] - ): - - contig_num = contig_names_rev[label] - - if contig_num in final_bins: - bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") - - # Close output files - for c in set(final_bins.values()): - bin_files[c].close() - - for b in range(len(bins)): - - for contig in bins[b]: - line = [] - line.append(contig_descriptions[contig_names[contig]]) - line.append(bins_list[b]) - output_bins.append(line) - - with open(output_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - for row in output_bins: - output_writer.writerow(row) - - logger.info("Final binning results can be found in " + str(output_bins_path)) - - unbinned_contigs = [] - - for i in range(node_count): - if i in remove_labels or i not in non_isolated: - line = [] - line.append(contig_names[i]) - unbinned_contigs.append(line) - - if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" - - with open(unbinned_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - - for row in unbinned_contigs: - output_writer.writerow(row) - - logger.info("Unbinned contigs can be found at " + unbinned_file) + write_output( + output_path, + prefix, + final_bins, + contigs_file, + contig_names.inverse, + bins, + contig_names, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, + contig_descriptions, + ) # Exit program # -------------- diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/utils/graphbin_SPAdes.py index 5ef4355..beb887b 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/utils/graphbin_SPAdes.py @@ -11,23 +11,17 @@ graphbin_SPAdes.py makes use of the assembly graphs produced by SPAdes. """ -import csv import logging -import os -import re -import subprocess -import sys import time -from collections import defaultdict - -from cogent3.parse.fasta import MinimalFastaParser -from igraph import * - -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap -from graphbin.utils.graphbin_Func import getClosestLabelledVertices +from graphbin.utils.graphbin_Func import graphbin_main from graphbin.utils.graphbin_Options import PARSER -from graphbin.utils.labelpropagation.labelprop import LabelProp +from graphbin.utils.parsers import get_initial_bin_count +from graphbin.utils.parsers.spades_parser import ( + parse_graph, + get_initial_binning_result, + write_output, +) __author__ = "Vijini Mallawaarachchi" @@ -43,6 +37,7 @@ def run(args): # Setup logger # ----------------------- + logger = logging.getLogger("GraphBin %s" % __version__) logger.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") @@ -62,7 +57,6 @@ def run(args): delimiter = args.delimiter max_iteration = args.max_iteration diff_threshold = args.diff_threshold - MIN_BIN_COUNT = 10 # Setup output path for log file # --------------------------------------------------- @@ -92,569 +86,57 @@ def run(args): # Get the number of bins from the initial binning result # --------------------------------------------------- - n_bins = 0 - - try: - all_bins_list = [] - - with open(contig_bins_file) as csvfile: - readCSV = csv.reader(csvfile, delimiter=delimiter) - for row in readCSV: - all_bins_list.append(row[1]) - - bins_list = list(set(all_bins_list)) - bins_list.sort() - - n_bins = len(bins_list) - logger.info( - "Number of bins available in the initial binning result: " + str(n_bins) - ) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the initial binning result file is provided and it is having the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Constructing the assembly graph") - - # Get contig paths from contigs.paths - # ------------------------------------- - - paths = {} - segment_contigs = {} - node_count = 0 - - contig_names = BidirectionalMap() - - my_map = BidirectionalMap() - - current_contig_num = "" - - try: - with open(contig_paths) as file: - name = file.readline() - path = file.readline() - - while name != "" and path != "": - - while ";" in path: - path = path[:-2] + "," + file.readline() - - start = "NODE_" - end = "_length_" - contig_num = str( - int(re.search("%s(.*)%s" % (start, end), name).group(1)) - ) - - segments = path.rstrip().split(",") + n_bins, bins_list = get_initial_bin_count(contig_bins_file, delimiter) - if current_contig_num != contig_num: - my_map[node_count] = int(contig_num) - current_contig_num = contig_num - contig_names[node_count] = name.strip() - node_count += 1 + # Get assembly graph + # -------------------- - if contig_num not in paths: - paths[contig_num] = [segments[0], segments[-1]] - - for segment in segments: - if segment not in segment_contigs: - segment_contigs[segment] = set([contig_num]) - else: - segment_contigs[segment].add(contig_num) - - name = file.readline() - path = file.readline() - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the contig paths file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - contigs_map = my_map - contigs_map_rev = my_map.inverse - - contig_names_rev = contig_names.inverse - - logger.info("Total number of contigs available: " + str(node_count)) - - links = [] - links_map = defaultdict(set) - - ## Construct the assembly graph - # ------------------------------- - - try: - # Get links from assembly_graph_with_scaffolds.gfa - with open(assembly_graph_file) as file: - - for line in file.readlines(): - line = line.strip() - - # Identify lines with link information - if line.startswith("L"): - strings = line.split("\t") - f1, f2 = strings[1] + strings[2], strings[3] + strings[4] - links_map[f1].add(f2) - links_map[f2].add(f1) - links.append( - strings[1] + strings[2] + " " + strings[3] + strings[4] - ) - - # Create graph - assembly_graph = Graph() - - # Add vertices - assembly_graph.add_vertices(node_count) - - # Create list of edges - edge_list = [] - - # Name vertices - for i in range(node_count): - assembly_graph.vs[i]["id"] = i - assembly_graph.vs[i]["label"] = str(contigs_map[i]) - - for i in range(len(paths)): - segments = paths[str(contigs_map[i])] - - start = segments[0] - start_rev = "" - - if start.endswith("+"): - start_rev = start[:-1] + "-" - else: - start_rev = start[:-1] + "+" - - end = segments[1] - end_rev = "" - - if end.endswith("+"): - end_rev = end[:-1] + "-" - else: - end_rev = end[:-1] + "+" - - new_links = [] - - if start in links_map: - new_links.extend(list(links_map[start])) - if start_rev in links_map: - new_links.extend(list(links_map[start_rev])) - if end in links_map: - new_links.extend(list(links_map[end])) - if end_rev in links_map: - new_links.extend(list(links_map[end_rev])) - - for new_link in new_links: - if new_link in segment_contigs: - for contig in segment_contigs[new_link]: - if i != contigs_map_rev[int(contig)]: - # Add edge to list of edges - edge_list.append((i, contigs_map_rev[int(contig)])) - - # Add edges to the graph - assembly_graph.add_edges(edge_list) - assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that the correct path to the assembly graph file is provided." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + assembly_graph, contigs_map, contig_names, node_count = parse_graph( + assembly_graph_file, contig_paths + ) # Get initial binning result # ---------------------------- - logger.info("Obtaining the initial binning result") - - bins = [[] for x in range(n_bins)] - - try: - with open(contig_bins_file) as contig_bins: - readCSV = csv.reader(contig_bins, delimiter=delimiter) - for row in readCSV: - start = "NODE_" - end = "_length_" - contig_num = contigs_map_rev[ - int(re.search("%s(.*)%s" % (start, end), row[0]).group(1)) - ] - - bin_num = bins_list.index(row[1]) - bins[bin_num].append(contig_num) - - except BaseException as err: - logger.error(f"Unexpected {err}") - logger.error( - "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Remove labels of ambiguous vertices - # ------------------------------------- - - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - neighbours_have_same_label_list = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - # Get set of closest labelled vertices with distance = 1 - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - neighbours_binned = False - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - neighbours_binned = True - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - elif neighbours_binned: - neighbours_have_same_label_list.append(i) - - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - # Further remove labels of ambiguous vertices - binned_contigs = [] - - for n in range(n_bins): - binned_contigs = sorted(binned_contigs + bins[n]) - - for b in range(n_bins): - - for i in bins[b]: - - if i not in neighbours_have_same_label_list: - - my_bin = b - - # Get set of closest labelled vertices - closest_neighbours = getClosestLabelledVertices( - assembly_graph, i, binned_contigs - ) - - if len(closest_neighbours) > 0: - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label and i not in remove_labels: - if my_bin in remove_by_bin: - if ( - len(bins[my_bin]) - len(remove_by_bin[my_bin]) - >= MIN_BIN_COUNT - ): - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) - - logger.info("Obtaining the refined binning result") - - # Get vertices which are not isolated and not in components without any labels - # ----------------------------------------------------------------------------- - - logger.info( - "Deteremining vertices which are not isolated and not in components without any labels" + bins = get_initial_binning_result( + n_bins, bins_list, contig_bins_file, contigs_map.inverse, delimiter ) - non_isolated = [] - - for i in range(node_count): - - if i not in non_isolated and i in binned_contigs: - - component = [] - component.append(i) - length = len(component) - neighbours = assembly_graph.neighbors(i, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - component = list(set(component)) - - while length != len(component): - - length = len(component) - - for j in component: - - neighbours = assembly_graph.neighbors(j, mode=ALL) - - for neighbor in neighbours: - if neighbor not in component: - component.append(neighbor) - - labelled = False - for j in component: - if j in binned_contigs: - labelled = True - break - - if labelled: - for j in component: - if j not in non_isolated: - non_isolated.append(j) - - logger.info("Number of non-isolated contigs: " + str(len(non_isolated))) - - # Run label propagation - # ----------------------- - - data = [] - - for contig in range(node_count): - - # Consider vertices that are not isolated - - if contig in non_isolated: - line = [] - line.append(contig) - - assigned = False - - for i in range(n_bins): - if contig in bins[i]: - line.append(i + 1) - assigned = True - - if not assigned: - line.append(0) - - neighbours = assembly_graph.neighbors(contig, mode=ALL) - - neighs = [] - - for neighbour in neighbours: - n = [] - n.append(neighbour) - n.append(1.0) - neighs.append(n) - - line.append(neighs) - - data.append(line) - - # Check if initial binning result consists of contigs belonging to multiple bins - - multiple_bins = False - - for item in data: - if type(item[1]) is int and type(item[2]) is int: - multiple_bins = True - break - - if multiple_bins: - logger.error( - "Initial binning result consists of contigs belonging to multiple bins. Please make sure that each contig in the initial binning result belongs to only one bin." - ) - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Label propagation - - lp = LabelProp() - - lp.load_data_from_mem(data) - - logger.info( - "Starting label propagation with eps=" - + str(diff_threshold) - + " and max_iteration=" - + str(max_iteration) - ) - - ans = lp.run(diff_threshold, max_iteration, show_log=True, clean_result=False) - - logger.info("Obtaining Label Propagation result") - - for l in ans: - for i in range(n_bins): - if l[1] == i + 1 and l[0] not in bins[i]: - bins[i].append(l[0]) - - # Remove labels of ambiguous vertices + # Run GraphBin logic # ------------------------------------- - - logger.info("Determining ambiguous vertices") - - remove_by_bin = {} - - remove_labels = [] - - for b in range(n_bins): - - for i in bins[b]: - - my_bin = b - - closest_neighbours = assembly_graph.neighbors(i, mode=ALL) - - # Determine whether all the closest labelled vertices have the same label as its own - neighbours_have_same_label = True - - for neighbour in closest_neighbours: - for k in range(n_bins): - if neighbour in bins[k]: - if k != my_bin: - neighbours_have_same_label = False - break - - if not neighbours_have_same_label: - if my_bin in remove_by_bin: - if len(bins[my_bin]) - len(remove_by_bin[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin].append(i) - else: - if len(bins[my_bin]) >= MIN_BIN_COUNT: - remove_labels.append(i) - remove_by_bin[my_bin] = [i] - - logger.info("Removing labels of ambiguous vertices") - - # Remove labels of ambiguous vertices - for i in remove_labels: - for n in range(n_bins): - if i in bins[n]: - bins[n].remove(i) + + final_bins, remove_labels, non_isolated = graphbin_main( + n_bins, + bins, + bins_list, + assembly_graph, + node_count, + diff_threshold, + max_iteration, + ) elapsed_time = time.time() - start_time - logger.info("Obtaining the Final Refined Binning result") - - final_bins = {} - - for i in range(n_bins): - for contig in bins[i]: - final_bins[contig] = bins_list[i] - # Print elapsed time for the process logger.info("Elapsed time: " + str(elapsed_time) + " seconds") # Write result to output file # ----------------------------- - logger.info("Writing the Final Binning result to file") - - output_bins = [] - - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" - - if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) - - bin_files = {} - - for bin_name in set(final_bins.values()): - bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" - ) - - for label, seq in MinimalFastaParser(contigs_file): - - contig_num = contig_names_rev[label] - - if contig_num in final_bins: - bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") - - # Close output files - for c in set(final_bins.values()): - bin_files[c].close() - - for b in range(len(bins)): - - for contig in bins[b]: - line = [] - line.append(contig_names[contig]) - line.append(bins_list[b]) - output_bins.append(line) - - with open(output_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - for row in output_bins: - output_writer.writerow(row) - - logger.info("Final binning results can be found in " + str(output_bins_path)) - - unbinned_contigs = [] - - for i in range(node_count): - if i in remove_labels or i not in non_isolated: - line = [] - line.append(contig_names[i]) - unbinned_contigs.append(line) - - if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" - - with open(unbinned_file, mode="w") as out_file: - output_writer = csv.writer( - out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL - ) - - for row in unbinned_contigs: - output_writer.writerow(row) - - logger.info("Unbinned contigs can be found at " + unbinned_file) + write_output( + output_path, + prefix, + final_bins, + contigs_file, + contig_names.inverse, + bins, + contig_names, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, + ) # Exit program # -------------- diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/utils/labelpropagation/labelprop.py index fcd169b..0d6caf2 100644 --- a/src/graphbin/utils/labelpropagation/labelprop.py +++ b/src/graphbin/utils/labelpropagation/labelprop.py @@ -49,7 +49,6 @@ def initialize_env(self): self.labelled_size = 0 def setup_env(self): - # initialize vertex_in_adj_map for vertex_id in self.vertex_adj_map.keys(): if vertex_id not in self.vertex_in_adj_map: diff --git a/src/graphbin/utils/parsers/__init__.py b/src/graphbin/utils/parsers/__init__.py new file mode 100644 index 0000000..5423b36 --- /dev/null +++ b/src/graphbin/utils/parsers/__init__.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +import csv +import logging +import re +import sys + + +__author__ = "Vijini Mallawaarachchi" +__copyright__ = "Copyright 2019-2022, GraphBin Project" +__credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] +__license__ = "BSD-3" +__version__ = "1.6.1" +__maintainer__ = "Vijini Mallawaarachchi" +__email__ = "vijini.mallawaarachchi@anu.edu.au" +__status__ = "Production" + + +logger = logging.getLogger("GraphBin %s" % __version__) + + +def get_initial_bin_count(contig_bins_file, delimiter): + n_bins = 0 + + try: + all_bins_list = [] + + with open(contig_bins_file) as csvfile: + readCSV = csv.reader(csvfile, delimiter=delimiter) + for row in readCSV: + all_bins_list.append(row[1]) + + bins_list = list(set(all_bins_list)) + bins_list.sort() + + n_bins = len(bins_list) + logger.info( + "Number of bins available in the initial binning result: " + str(n_bins) + ) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the initial binning result file is provided and it is having the correct format." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + return n_bins, bins_list diff --git a/src/graphbin/utils/parsers/canu_parser.py b/src/graphbin/utils/parsers/canu_parser.py new file mode 100644 index 0000000..6ccc8bc --- /dev/null +++ b/src/graphbin/utils/parsers/canu_parser.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 + +import csv +import logging +import os +import subprocess +import sys + +from cogent3.parse.fasta import MinimalFastaParser +from igraph import * + +from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap + + +__author__ = "Vijini Mallawaarachchi" +__copyright__ = "Copyright 2019-2022, GraphBin Project" +__credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] +__license__ = "BSD-3" +__version__ = "1.6.1" +__maintainer__ = "Vijini Mallawaarachchi" +__email__ = "vijini.mallawaarachchi@anu.edu.au" +__status__ = "Production" + + +logger = logging.getLogger("GraphBin %s" % __version__) + + +def get_initial_binning_result( + n_bins, bins_list, contig_bins_file, contigs_map_rev, delimiter +): + logger.info("Obtaining the initial binning result") + + bins = [[] for x in range(n_bins)] + + try: + with open(contig_bins_file) as contig_bins: + readCSV = csv.reader(contig_bins, delimiter=delimiter) + for row in readCSV: + contig_num = contigs_map_rev[row[0]] + + bin_num = bins_list.index(row[1]) + bins[bin_num].append(contig_num) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + return bins + + +def parse_graph(assembly_graph_file): + # Get the links from the .gfa file + # ----------------------------------- + + my_map = BidirectionalMap() + + node_count = 0 + + nodes = [] + + links = [] + + try: + # Get contig connections from .gfa file + with open(assembly_graph_file) as file: + for line in file.readlines(): + line = line.strip() + + # Count the number of contigs + if line.startswith("S"): + strings = line.split("\t") + my_node = strings[1] + my_map[node_count] = my_node + nodes.append(my_node) + node_count += 1 + + # Identify lines with link information + elif line.startswith("L"): + link = [] + strings = line.split("\t") + + if strings[1] != strings[3]: + start = strings[1] + end = strings[3] + link.append(start) + link.append(end) + links.append(link) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the assembly graph file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + contigs_map = my_map + contigs_map_rev = my_map.inverse + + logger.info("Total number of contigs available: " + str(node_count)) + + ## Construct the assembly graph + # ------------------------------- + + try: + # Create the graph + assembly_graph = Graph() + + # Create list of edges + edge_list = [] + + # Add vertices + assembly_graph.add_vertices(node_count) + + # Name vertices + for i in range(len(assembly_graph.vs)): + assembly_graph.vs[i]["id"] = i + assembly_graph.vs[i]["label"] = str(contigs_map[i]) + + # Iterate links + for link in links: + # Remove self loops + if link[0] != link[1]: + # Add edge to list of edges + edge_list.append((contigs_map_rev[link[0]], contigs_map_rev[link[1]])) + + # Add edges to the graph + assembly_graph.add_edges(edge_list) + assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the assembly graph file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + + return assembly_graph, contigs_map, node_count + + +def write_output( + output_path, + prefix, + final_bins, + contigs_file, + contigs_map_rev, + bins, + contigs_map, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, +): + logger.info("Writing the Final Binning result to file") + + output_bins = [] + + output_bins_path = output_path + prefix + "bins/" + output_file = output_path + prefix + "graphbin_output.csv" + + if not os.path.isdir(output_bins_path): + subprocess.run("mkdir -p " + output_bins_path, shell=True) + + bin_files = {} + + for bin_name in set(final_bins.values()): + bin_files[bin_name] = open( + output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + ) + + for label, seq in MinimalFastaParser( + contigs_file, label_to_name=lambda x: x.split()[0] + ): + contig_num = contigs_map_rev[label] + + if contig_num in final_bins: + bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") + + # Close output files + for c in set(final_bins.values()): + bin_files[c].close() + + for b in range(len(bins)): + for contig in bins[b]: + line = [] + line.append(str(contigs_map[contig])) + line.append(bins_list[b]) + output_bins.append(line) + + with open(output_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + for row in output_bins: + output_writer.writerow(row) + + logger.info("Final binning results can be found in " + str(output_bins_path)) + + unbinned_contigs = [] + + for i in range(node_count): + if i in remove_labels or i not in non_isolated: + line = [] + line.append(str(contigs_map[i])) + unbinned_contigs.append(line) + + if len(unbinned_contigs) != 0: + unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + + with open(unbinned_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + + for row in unbinned_contigs: + output_writer.writerow(row) + + logger.info("Unbinned contigs can be found at " + unbinned_file) diff --git a/src/graphbin/utils/parsers/flye_parser.py b/src/graphbin/utils/parsers/flye_parser.py new file mode 100644 index 0000000..b5ea4c0 --- /dev/null +++ b/src/graphbin/utils/parsers/flye_parser.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 + +import csv +import logging +import os +import subprocess +import sys + +from collections import defaultdict + +from cogent3.parse.fasta import MinimalFastaParser +from igraph import * + +from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap + + +__author__ = "Vijini Mallawaarachchi" +__copyright__ = "Copyright 2019-2022, GraphBin Project" +__credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] +__license__ = "BSD-3" +__version__ = "1.6.1" +__maintainer__ = "Vijini Mallawaarachchi" +__email__ = "vijini.mallawaarachchi@anu.edu.au" +__status__ = "Production" + + +logger = logging.getLogger("GraphBin %s" % __version__) + + +def get_initial_binning_result( + n_bins, bins_list, contig_bins_file, contig_names_rev, delimiter +): + logger.info("Obtaining the initial binning result") + + bins = [[] for x in range(n_bins)] + + try: + with open(contig_bins_file) as contig_bins: + readCSV = csv.reader(contig_bins, delimiter=delimiter) + for row in readCSV: + contig_num = contig_names_rev[row[0]] + + bin_num = bins_list.index(row[1]) + bins[bin_num].append(contig_num) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + return bins + + +def parse_graph(assembly_graph_file, contig_paths): + # Get contig names + # ----------------------------------- + + contig_names = BidirectionalMap() + + contig_num = 0 + + with open(contig_paths, "r") as file: + for line in file.readlines(): + if not line.startswith("#"): + name = line.strip().split()[0] + contig_names[contig_num] = name + contig_num += 1 + + contig_names_rev = contig_names.inverse + + # Get the paths and edges + # ----------------------------------- + + paths = {} + segment_contigs = {} + + try: + with open(contig_paths) as file: + for line in file.readlines(): + if not line.startswith("#"): + strings = line.strip().split() + + contig_name = strings[0] + + path = strings[-1] + path = path.replace("*", "") + + if path.startswith(","): + path = path[1:] + + if path.endswith(","): + path = path[:-1] + + segments = path.rstrip().split(",") + + contig_num = contig_names_rev[contig_name] + + if contig_num not in paths: + paths[contig_num] = segments + + for segment in segments: + if segment != "": + if segment not in segment_contigs: + segment_contigs[segment] = set([contig_num]) + else: + segment_contigs[segment].add(contig_num) + + links_map = defaultdict(set) + + # Get links from assembly_graph.gfa + with open(assembly_graph_file) as file: + for line in file.readlines(): + line = line.strip() + + # Identify lines with link information + if line.startswith("L"): + strings = line.split("\t") + + f1, f2 = "", "" + + if strings[2] == "+": + f1 = strings[1][5:] + if strings[2] == "-": + f1 = "-" + strings[1][5:] + if strings[4] == "+": + f2 = strings[3][5:] + if strings[4] == "-": + f2 = "-" + strings[3][5:] + + links_map[f1].add(f2) + links_map[f2].add(f1) + + # Create list of edges + edge_list = [] + + for i in paths: + segments = paths[i] + + new_links = [] + + for segment in segments: + my_segment = segment + my_segment_num = "" + + my_segment_rev = "" + + if my_segment.startswith("-"): + my_segment_rev = my_segment[1:] + my_segment_num = my_segment[1:] + else: + my_segment_rev = "-" + my_segment + my_segment_num = my_segment + + if my_segment in links_map: + new_links.extend(list(links_map[my_segment])) + + if my_segment_rev in links_map: + new_links.extend(list(links_map[my_segment_rev])) + + if my_segment in segment_contigs: + for contig in segment_contigs[my_segment]: + if i != contig: + # Add edge to list of edges + edge_list.append((i, contig)) + + if my_segment_rev in segment_contigs: + for contig in segment_contigs[my_segment_rev]: + if i != contig: + # Add edge to list of edges + edge_list.append((i, contig)) + + if my_segment_num in segment_contigs: + for contig in segment_contigs[my_segment_num]: + if i != contig: + # Add edge to list of edges + edge_list.append((i, contig)) + + for new_link in new_links: + if new_link in segment_contigs: + for contig in segment_contigs[new_link]: + if i != contig: + # Add edge to list of edges + edge_list.append((i, contig)) + + if new_link.startswith("-"): + if new_link[1:] in segment_contigs: + for contig in segment_contigs[new_link[1:]]: + if i != contig: + # Add edge to list of edges + edge_list.append((i, contig)) + + node_count = len(contig_names_rev) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the assembly graph file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + logger.info("Total number of contigs available: " + str(node_count)) + + ## Construct the assembly graph + # ------------------------------- + + try: + # Create the graph + assembly_graph = Graph() + + # Add vertices + assembly_graph.add_vertices(node_count) + + # Name vertices + for i in range(len(assembly_graph.vs)): + assembly_graph.vs[i]["id"] = i + assembly_graph.vs[i]["label"] = str(contig_names[i]) + + # Add edges to the graph + assembly_graph.add_edges(edge_list) + assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the assembly graph file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + + return assembly_graph, contig_names, node_count + + +def write_output( + output_path, + prefix, + final_bins, + contigs_file, + contig_names_rev, + bins, + contig_names, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, +): + logger.info("Writing the Final Binning result to file") + + output_bins = [] + + output_bins_path = output_path + prefix + "bins/" + output_file = output_path + prefix + "graphbin_output.csv" + + if not os.path.isdir(output_bins_path): + subprocess.run("mkdir -p " + output_bins_path, shell=True) + + bin_files = {} + + for bin_name in set(final_bins.values()): + bin_files[bin_name] = open( + output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + ) + + for label, seq in MinimalFastaParser(contigs_file): + contig_num = contig_names_rev[label] + + if contig_num in final_bins: + bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") + + # Close output files + for c in set(final_bins.values()): + bin_files[c].close() + + for b in range(len(bins)): + # with open(output_bins_path + "bin_" + str(b+1) + "_ids.txt", "w") as bin_file: + for contig in bins[b]: + line = [] + line.append(str(contig_names[contig])) + line.append(bins_list[b]) + output_bins.append(line) + # bin_file.write(str(contig_names[contig])+"\n") + + # subprocess.run("awk -F'>' 'NR==FNR{ids[$0]; next} NF>1{f=($2 in ids)} f' " + output_bins_path + "bin_" + str(b+1) + "_ids.txt " + contigs_file + " > " + output_bins_path + "bin_" +str(b+1) +"_seqs.fasta", shell=True) + + with open(output_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + for row in output_bins: + output_writer.writerow(row) + + logger.info("Final binning results can be found in " + str(output_bins_path)) + + unbinned_contigs = [] + + for i in range(node_count): + if i in remove_labels or i not in non_isolated: + line = [] + line.append(str(contig_names[i])) + unbinned_contigs.append(line) + + if len(unbinned_contigs) != 0: + unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + + with open(unbinned_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + + for row in unbinned_contigs: + output_writer.writerow(row) + + logger.info("Unbinned contigs can be found at " + unbinned_file) diff --git a/src/graphbin/utils/parsers/megahit_parser.py b/src/graphbin/utils/parsers/megahit_parser.py new file mode 100644 index 0000000..83e7a2c --- /dev/null +++ b/src/graphbin/utils/parsers/megahit_parser.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 + +import csv +import logging +import os +import re +import subprocess +import sys + +from cogent3.parse.fasta import MinimalFastaParser +from igraph import * + +from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap + + +__author__ = "Vijini Mallawaarachchi" +__copyright__ = "Copyright 2019-2022, GraphBin Project" +__credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] +__license__ = "BSD-3" +__version__ = "1.6.1" +__maintainer__ = "Vijini Mallawaarachchi" +__email__ = "vijini.mallawaarachchi@anu.edu.au" +__status__ = "Production" + + +logger = logging.getLogger("GraphBin %s" % __version__) + + +def get_initial_binning_result( + n_bins, + bins_list, + contig_bins_file, + contigs_map_rev, + graph_to_contig_map_rev, + delimiter, +): + logger.info("Obtaining the initial binning result") + + bins = [[] for x in range(n_bins)] + + try: + with open(contig_bins_file) as contig_bins: + readCSV = csv.reader(contig_bins, delimiter=delimiter) + for row in readCSV: + contig_num = contigs_map_rev[int(graph_to_contig_map_rev[row[0]])] + + bin_num = bins_list.index(row[1]) + bins[bin_num].append(contig_num) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + return bins + + +def parse_graph(assembly_graph_file, original_contigs): + node_count = 0 + + graph_contigs = {} + + links = [] + + my_map = BidirectionalMap() + + try: + # Get links from .gfa file + with open(assembly_graph_file) as file: + for line in file.readlines(): + line = line.strip() + + # Identify lines with link information + if line.startswith("L"): + link = [] + + strings = line.split("\t") + + start_1 = "NODE_" + end_1 = "_length" + + link1 = int( + re.search("%s(.*)%s" % (start_1, end_1), strings[1]).group(1) + ) + + start_2 = "NODE_" + end_2 = "_length" + + link2 = int( + re.search("%s(.*)%s" % (start_2, end_2), strings[3]).group(1) + ) + + link.append(link1) + link.append(link2) + links.append(link) + + elif line.startswith("S"): + strings = line.split() + + start = "NODE_" + end = "_length" + + contig_num = int( + re.search("%s(.*)%s" % (start, end), strings[1]).group(1) + ) + + my_map[node_count] = int(contig_num) + + graph_contigs[contig_num] = strings[2] + + node_count += 1 + + logger.info("Total number of contigs available: " + str(node_count)) + + contigs_map = my_map + contigs_map_rev = my_map.inverse + + # Create graph + assembly_graph = Graph() + + # Add vertices + assembly_graph.add_vertices(node_count) + + # Create list of edges + edge_list = [] + + for i in range(node_count): + assembly_graph.vs[i]["id"] = i + assembly_graph.vs[i]["label"] = str(contigs_map[i]) + + # Iterate links + for link in links: + # Remove self loops + if link[0] != link[1]: + # Add edge to list of edges + edge_list.append((contigs_map_rev[link[0]], contigs_map_rev[link[1]])) + + # Add edges to the graph + assembly_graph.add_edges(edge_list) + assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the assembly graph file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + + # Map original contig IDs to contig IDS of assembly graph + # -------------------------------------------------------- + + graph_to_contig_map = BidirectionalMap() + + for (n, m), (n2, m2) in zip(graph_contigs.items(), original_contigs.items()): + if m == m2: + graph_to_contig_map[n] = n2 + + return assembly_graph, graph_to_contig_map, contigs_map, node_count + + +def write_output( + output_path, + prefix, + final_bins, + contigs_file, + graph_to_contig_map, + bins, + contigs_map, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, +): + logger.info("Writing the Final Binning result to file") + + output_bins = [] + graph_to_contig_map_rev = graph_to_contig_map.inverse + contigs_map_rev = contigs_map.inverse + + output_bins_path = output_path + prefix + "bins/" + output_file = output_path + prefix + "graphbin_output.csv" + + if not os.path.isdir(output_bins_path): + subprocess.run("mkdir -p " + output_bins_path, shell=True) + + bin_files = {} + + for bin_name in set(final_bins.values()): + bin_files[bin_name] = open( + output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + ) + + for label, seq in MinimalFastaParser( + contigs_file, label_to_name=lambda x: x.split()[0] + ): + contig_num = contigs_map_rev[graph_to_contig_map_rev[label]] + + if contig_num in final_bins: + bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") + + # Close output files + for c in set(final_bins.values()): + bin_files[c].close() + + for b in range(len(bins)): + for contig in bins[b]: + line = [] + line.append(graph_to_contig_map[contigs_map[contig]]) + line.append(bins_list[b]) + output_bins.append(line) + + with open(output_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + for row in output_bins: + output_writer.writerow(row) + + logger.info("Final binning results can be found in " + str(output_bins_path)) + + unbinned_contigs = [] + + for i in range(node_count): + if i in remove_labels or i not in non_isolated: + line = [] + line.append(graph_to_contig_map[contigs_map[i]]) + unbinned_contigs.append(line) + + if len(unbinned_contigs) != 0: + unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + + with open(unbinned_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + + for row in unbinned_contigs: + output_writer.writerow(row) + + logger.info("Unbinned contigs can be found at " + unbinned_file) + + +def get_contig_descriptors(contigs_file): + original_contigs = {} + contig_descriptions = {} + + for label, seq in MinimalFastaParser(contigs_file): + name = label.split()[0] + original_contigs[name] = seq + contig_descriptions[name] = label + + return original_contigs diff --git a/src/graphbin/utils/parsers/miniasm_parser.py b/src/graphbin/utils/parsers/miniasm_parser.py new file mode 100644 index 0000000..92a59df --- /dev/null +++ b/src/graphbin/utils/parsers/miniasm_parser.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 + +import csv +import logging +import os +import subprocess +import sys + +from cogent3.parse.fasta import MinimalFastaParser +from igraph import * + +from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap + + +__author__ = "Vijini Mallawaarachchi" +__copyright__ = "Copyright 2019-2022, GraphBin Project" +__credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] +__license__ = "BSD-3" +__version__ = "1.6.1" +__maintainer__ = "Vijini Mallawaarachchi" +__email__ = "vijini.mallawaarachchi@anu.edu.au" +__status__ = "Production" + + +logger = logging.getLogger("GraphBin %s" % __version__) + + +def get_initial_binning_result( + n_bins, bins_list, contig_bins_file, contigs_map_rev, delimiter +): + logger.info("Obtaining the initial binning result") + + bins = [[] for x in range(n_bins)] + + try: + with open(contig_bins_file) as contig_bins: + readCSV = csv.reader(contig_bins, delimiter=delimiter) + for row in readCSV: + contig_num = contigs_map_rev[row[0]] + + bin_num = bins_list.index(row[1]) + bins[bin_num].append(contig_num) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + return bins + + +def parse_graph(assembly_graph_file): + # Get the links from the .gfa file + # ----------------------------------- + + my_map = BidirectionalMap() + + node_count = 0 + + nodes = [] + + links = [] + + try: + # Get contig connections from .gfa file + with open(assembly_graph_file) as file: + for line in file.readlines(): + line = line.strip() + + # Count the number of contigs + if line.startswith("S"): + strings = line.split("\t") + my_node = strings[1] + my_map[node_count] = my_node + nodes.append(my_node) + node_count += 1 + + # Identify lines with link information + elif line.startswith("L"): + link = [] + strings = line.split("\t") + + if strings[1] != strings[3]: + start = strings[1] + end = strings[3] + link.append(start) + link.append(end) + links.append(link) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the assembly graph file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + contigs_map = my_map + contigs_map_rev = my_map.inverse + + logger.info("Total number of contigs available: " + str(node_count)) + + ## Construct the assembly graph + # ------------------------------- + + try: + # Create the graph + assembly_graph = Graph() + + # Create list of edges + edge_list = [] + + # Add vertices + assembly_graph.add_vertices(node_count) + + # Name vertices + for i in range(len(assembly_graph.vs)): + assembly_graph.vs[i]["id"] = i + assembly_graph.vs[i]["label"] = str(contigs_map[i]) + + # Iterate links + for link in links: + # Remove self loops + if link[0] != link[1]: + # Add edge to list of edges + edge_list.append((contigs_map_rev[link[0]], contigs_map_rev[link[1]])) + + # Add edges to the graph + assembly_graph.add_edges(edge_list) + assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the assembly graph file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + + return assembly_graph, contigs_map, node_count + + +def write_output( + output_path, + prefix, + final_bins, + contigs_file, + contigs_map, + bins, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, +): + logger.info("Writing the Final Binning result to file") + + output_bins = [] + contigs_map_rev = contigs_map.inverse + + output_bins_path = output_path + prefix + "bins/" + output_file = output_path + prefix + "graphbin_output.csv" + + if not os.path.isdir(output_bins_path): + subprocess.run("mkdir -p " + output_bins_path, shell=True) + + bin_files = {} + + for bin_name in set(final_bins.values()): + bin_files[bin_name] = open( + output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + ) + + for label, seq in MinimalFastaParser(contigs_file): + contig_num = contigs_map_rev[label] + + if contig_num in final_bins: + bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") + + # Close output files + for c in set(final_bins.values()): + bin_files[c].close() + + for b in range(len(bins)): + for contig in bins[b]: + line = [] + line.append(str(contigs_map[contig])) + line.append(bins_list[b]) + output_bins.append(line) + + with open(output_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + for row in output_bins: + output_writer.writerow(row) + + logger.info("Final binning results can be found in " + str(output_bins_path)) + + unbinned_contigs = [] + + for i in range(node_count): + if i in remove_labels or i not in non_isolated: + line = [] + line.append(str(contigs_map[i])) + unbinned_contigs.append(line) + + if len(unbinned_contigs) != 0: + unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + + with open(unbinned_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + + for row in unbinned_contigs: + output_writer.writerow(row) + + logger.info("Unbinned contigs can be found at " + unbinned_file) diff --git a/src/graphbin/utils/parsers/sga_parser.py b/src/graphbin/utils/parsers/sga_parser.py new file mode 100644 index 0000000..c84dfde --- /dev/null +++ b/src/graphbin/utils/parsers/sga_parser.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 + +import csv +import logging +import os +import re +import subprocess +import sys + +from cogent3.parse.fasta import MinimalFastaParser +from igraph import * + +from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap + + +__author__ = "Vijini Mallawaarachchi" +__copyright__ = "Copyright 2019-2022, GraphBin Project" +__credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] +__license__ = "BSD-3" +__version__ = "1.6.1" +__maintainer__ = "Vijini Mallawaarachchi" +__email__ = "vijini.mallawaarachchi@anu.edu.au" +__status__ = "Production" + + +logger = logging.getLogger("GraphBin %s" % __version__) + + +def get_initial_binning_result( + n_bins, bins_list, contig_bins_file, contigs_map_rev, delimiter +): + logger.info("Obtaining the initial binning result") + + bins = [[] for x in range(n_bins)] + + try: + with open(contig_bins_file) as contig_bins: + readCSV = csv.reader(contig_bins, delimiter=delimiter) + for row in readCSV: + start = "contig-" + end = "" + contig_num = contigs_map_rev[ + int(re.search("%s(.*)%s" % (start, end), row[0]).group(1)) + ] + + bin_num = bins_list.index(row[1]) + bins[bin_num].append(contig_num) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + return bins + + +def parse_graph(assembly_graph_file): + links = [] + + contig_names = BidirectionalMap() + + my_map = BidirectionalMap() + + node_count = 0 + + try: + # Get contig connections from .asqg file + with open(assembly_graph_file) as file: + for line in file.readlines(): + line = line.strip() + + # Count the number of contigs + if line.startswith("VT"): + start = "contig-" + end = "" + contig_name = str(line.split()[1]) + contig_num = int( + re.search("%s(.*)%s" % (start, end), contig_name).group(1) + ) + my_map[node_count] = contig_num + contig_names[node_count] = contig_name.strip() + node_count += 1 + + # Identify lines with link information + elif line.startswith("ED"): + link = [] + strings = line.split("\t")[1].split() + link.append(int(strings[0][7:])) + link.append(int(strings[1][7:])) + links.append(link) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the assembly graph file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + contigs_map = my_map + contigs_map_rev = my_map.inverse + + contig_names_rev = contig_names.inverse + + logger.info("Total number of contigs available: " + str(node_count)) + + try: + # Create the graph + assembly_graph = Graph() + + # Create list of edges + edge_list = [] + + # Add vertices + assembly_graph.add_vertices(node_count) + + # Name vertices + for i in range(len(assembly_graph.vs)): + assembly_graph.vs[i]["id"] = i + assembly_graph.vs[i]["label"] = str(contigs_map[i]) + + # Iterate links + for link in links: + # Remove self loops + if link[0] != link[1]: + # Add edge to list of edges + edge_list.append((contigs_map_rev[link[0]], contigs_map_rev[link[1]])) + + # Add edges to the graph + assembly_graph.add_edges(edge_list) + assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the assembly graph file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + + return assembly_graph, contigs_map, contig_names, node_count + + +def write_output( + output_path, + prefix, + final_bins, + contigs_file, + contig_names_rev, + bins, + contig_names, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, + contig_descriptions, +): + logger.info("Writing the Final Binning result to file") + + output_bins = [] + + output_bins_path = output_path + prefix + "bins/" + output_file = output_path + prefix + "graphbin_output.csv" + + if not os.path.isdir(output_bins_path): + subprocess.run("mkdir -p " + output_bins_path, shell=True) + + bin_files = {} + + for bin_name in set(final_bins.values()): + bin_files[bin_name] = open( + output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + ) + + for label, seq in MinimalFastaParser( + contigs_file, label_to_name=lambda x: x.split()[0] + ): + contig_num = contig_names_rev[label] + + if contig_num in final_bins: + bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") + + # Close output files + for c in set(final_bins.values()): + bin_files[c].close() + + for b in range(len(bins)): + for contig in bins[b]: + line = [] + line.append(contig_descriptions[contig_names[contig]]) + line.append(bins_list[b]) + output_bins.append(line) + + with open(output_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + for row in output_bins: + output_writer.writerow(row) + + logger.info("Final binning results can be found in " + str(output_bins_path)) + + unbinned_contigs = [] + + for i in range(node_count): + if i in remove_labels or i not in non_isolated: + line = [] + line.append(contig_names[i]) + unbinned_contigs.append(line) + + if len(unbinned_contigs) != 0: + unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + + with open(unbinned_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + + for row in unbinned_contigs: + output_writer.writerow(row) + + logger.info("Unbinned contigs can be found at " + unbinned_file) + + +def get_contig_descriptions(contigs_file): + contig_descriptions = {} + + for label, _ in MinimalFastaParser(contigs_file): + name = label.split()[0] + contig_descriptions[name] = label + + return contig_descriptions diff --git a/src/graphbin/utils/parsers/spades_parser.py b/src/graphbin/utils/parsers/spades_parser.py new file mode 100644 index 0000000..d6808ba --- /dev/null +++ b/src/graphbin/utils/parsers/spades_parser.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 + +import csv +import logging +import os +import re +import subprocess +import sys + +from collections import defaultdict + +from cogent3.parse.fasta import MinimalFastaParser +from igraph import * + +from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap + + +__author__ = "Vijini Mallawaarachchi" +__copyright__ = "Copyright 2019-2022, GraphBin Project" +__credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] +__license__ = "BSD-3" +__version__ = "1.6.1" +__maintainer__ = "Vijini Mallawaarachchi" +__email__ = "vijini.mallawaarachchi@anu.edu.au" +__status__ = "Production" + + +logger = logging.getLogger("GraphBin %s" % __version__) + + +def get_initial_binning_result( + n_bins, bins_list, contig_bins_file, contigs_map_rev, delimiter +): + logger.info("Obtaining the initial binning result") + + bins = [[] for x in range(n_bins)] + + try: + with open(contig_bins_file) as contig_bins: + readCSV = csv.reader(contig_bins, delimiter=delimiter) + for row in readCSV: + start = "NODE_" + end = "_length_" + contig_num = contigs_map_rev[ + int(re.search("%s(.*)%s" % (start, end), row[0]).group(1)) + ] + + bin_num = bins_list.index(row[1]) + bins[bin_num].append(contig_num) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that you have provided the correct assembler type and the correct path to the binning result file in the correct format." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + return bins + + +def parse_graph(assembly_graph_file, contig_paths): + paths = {} + segment_contigs = {} + node_count = 0 + + contig_names = BidirectionalMap() + + my_map = BidirectionalMap() + + current_contig_num = "" + + try: + with open(contig_paths) as file: + name = file.readline() + path = file.readline() + + while name != "" and path != "": + while ";" in path: + path = path[:-2] + "," + file.readline() + + start = "NODE_" + end = "_length_" + contig_num = str( + int(re.search("%s(.*)%s" % (start, end), name).group(1)) + ) + + segments = path.rstrip().split(",") + + if current_contig_num != contig_num: + my_map[node_count] = int(contig_num) + current_contig_num = contig_num + contig_names[node_count] = name.strip() + node_count += 1 + + if contig_num not in paths: + paths[contig_num] = [segments[0], segments[-1]] + + for segment in segments: + if segment not in segment_contigs: + segment_contigs[segment] = set([contig_num]) + else: + segment_contigs[segment].add(contig_num) + + name = file.readline() + path = file.readline() + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the contig paths file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + contigs_map = my_map + contigs_map_rev = my_map.inverse + + logger.info("Total number of contigs available: " + str(node_count)) + + links = [] + links_map = defaultdict(set) + + ## Construct the assembly graph + # ------------------------------- + + try: + # Get links from assembly_graph_with_scaffolds.gfa + with open(assembly_graph_file) as file: + for line in file.readlines(): + line = line.strip() + + # Identify lines with link information + if line.startswith("L"): + strings = line.split("\t") + f1, f2 = strings[1] + strings[2], strings[3] + strings[4] + links_map[f1].add(f2) + links_map[f2].add(f1) + links.append( + strings[1] + strings[2] + " " + strings[3] + strings[4] + ) + + # Create graph + assembly_graph = Graph() + + # Add vertices + assembly_graph.add_vertices(node_count) + + # Create list of edges + edge_list = [] + + # Name vertices + for i in range(node_count): + assembly_graph.vs[i]["id"] = i + assembly_graph.vs[i]["label"] = str(contigs_map[i]) + + for i in range(len(paths)): + segments = paths[str(contigs_map[i])] + + start = segments[0] + start_rev = "" + + if start.endswith("+"): + start_rev = start[:-1] + "-" + else: + start_rev = start[:-1] + "+" + + end = segments[1] + end_rev = "" + + if end.endswith("+"): + end_rev = end[:-1] + "-" + else: + end_rev = end[:-1] + "+" + + new_links = [] + + if start in links_map: + new_links.extend(list(links_map[start])) + if start_rev in links_map: + new_links.extend(list(links_map[start_rev])) + if end in links_map: + new_links.extend(list(links_map[end])) + if end_rev in links_map: + new_links.extend(list(links_map[end_rev])) + + for new_link in new_links: + if new_link in segment_contigs: + for contig in segment_contigs[new_link]: + if i != contigs_map_rev[int(contig)]: + # Add edge to list of edges + edge_list.append((i, contigs_map_rev[int(contig)])) + + # Add edges to the graph + assembly_graph.add_edges(edge_list) + assembly_graph.simplify(multiple=True, loops=False, combine_edges=None) + + except BaseException as err: + logger.error(f"Unexpected {err}") + logger.error( + "Please make sure that the correct path to the assembly graph file is provided." + ) + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + + return assembly_graph, contigs_map, contig_names, node_count + + +def write_output( + output_path, + prefix, + final_bins, + contigs_file, + contig_names_rev, + bins, + contig_names, + bins_list, + delimiter, + node_count, + remove_labels, + non_isolated, +): + logger.info("Writing the Final Binning result to file") + + output_bins = [] + + output_bins_path = output_path + prefix + "bins/" + output_file = output_path + prefix + "graphbin_output.csv" + + if not os.path.isdir(output_bins_path): + subprocess.run("mkdir -p " + output_bins_path, shell=True) + + bin_files = {} + + for bin_name in set(final_bins.values()): + bin_files[bin_name] = open( + output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + ) + + for label, seq in MinimalFastaParser(contigs_file): + contig_num = contig_names_rev[label] + + if contig_num in final_bins: + bin_files[final_bins[contig_num]].write(f">{label}\n{seq}\n") + + # Close output files + for c in set(final_bins.values()): + bin_files[c].close() + + for b in range(len(bins)): + for contig in bins[b]: + line = [] + line.append(contig_names[contig]) + line.append(bins_list[b]) + output_bins.append(line) + + with open(output_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + for row in output_bins: + output_writer.writerow(row) + + logger.info("Final binning results can be found in " + str(output_bins_path)) + + unbinned_contigs = [] + + for i in range(node_count): + if i in remove_labels or i not in non_isolated: + line = [] + line.append(contig_names[i]) + unbinned_contigs.append(line) + + if len(unbinned_contigs) != 0: + unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + + with open(unbinned_file, mode="w") as out_file: + output_writer = csv.writer( + out_file, delimiter=delimiter, quotechar='"', quoting=csv.QUOTE_MINIMAL + ) + + for row in unbinned_contigs: + output_writer.writerow(row) + + logger.info("Unbinned contigs can be found at " + unbinned_file) From 4b984cb1859cfc89fd7af624dc7277c86e096e77 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Sun, 5 Mar 2023 17:15:27 +1030 Subject: [PATCH 33/76] DOC: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f42434..c5fefae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- Final Labelling + GraphBin logo

# GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs From 65751eaf1acb595fb3c3dcef8b12de1739132b0a Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 5 Apr 2023 16:12:09 +0930 Subject: [PATCH 34/76] DOC: Update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c5fefae..e852163 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![DOI](https://img.shields.io/badge/DOI-10.1093/bioinformatics/btaa180-informational)](https://doi.org/10.1093/bioinformatics/btaa180) [![Anaconda-Server Badge](https://anaconda.org/bioconda/graphbin/badges/version.svg)](https://anaconda.org/bioconda/graphbin) +[![Anaconda-Server Badge](https://anaconda.org/bioconda/graphbin/badges/downloads.svg)](https://anaconda.org/bioconda/graphbin) [![PyPI version](https://badge.fury.io/py/graphbin.svg)](https://badge.fury.io/py/graphbin) [![Downloads](https://static.pepy.tech/badge/graphbin)](https://pepy.tech/project/graphbin) From 78f2a44b6dcb572b2036b413e3359bee69d6369f Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 5 May 2023 15:45:33 +0930 Subject: [PATCH 35/76] DOC: Add CZI logo --- README.md | 8 ++++++++ images/czi-logo.png | Bin 0 -> 51229 bytes 2 files changed, 8 insertions(+) create mode 100644 images/czi-logo.png diff --git a/README.md b/README.md index e852163..fc52618 100644 --- a/README.md +++ b/README.md @@ -140,3 +140,11 @@ Vijini Mallawaarachchi, Anuradha Wickramarachchi, Yu Lin. GraphBin: Refined binn eprint = {https://academic.oup.com/bioinformatics/article-pdf/36/11/3307/33329097/btaa180.pdf}, } ``` + +## Funding + +GraphBin is funded by a [Essential Open Source Software for Science Grant](https://chanzuckerberg.com/eoss/proposals/cogent3-python-apis-for-iq-tree-and-graphbin-via-a-plug-in-architecture/) from the Chan Zuckerberg Initiative. + +

+ +

diff --git a/images/czi-logo.png b/images/czi-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ed780a516bf14c5c9fe6f9f4b3c71bdf8a1022b3 GIT binary patch literal 51229 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 S?r-1#2uV>{kt!km!2bnu$MNg{ literal 0 HcmV?d00001 From e291d24bea77d7f17fe7dbfcd41e4a7298b83445 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 10 May 2023 11:52:40 +0930 Subject: [PATCH 36/76] DOC: Update CZI logo --- README.md | 2 +- images/czi-logo.png | Bin 51229 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 images/czi-logo.png diff --git a/README.md b/README.md index fc52618..0f1d52c 100644 --- a/README.md +++ b/README.md @@ -146,5 +146,5 @@ Vijini Mallawaarachchi, Anuradha Wickramarachchi, Yu Lin. GraphBin: Refined binn GraphBin is funded by a [Essential Open Source Software for Science Grant](https://chanzuckerberg.com/eoss/proposals/cogent3-python-apis-for-iq-tree-and-graphbin-via-a-plug-in-architecture/) from the Chan Zuckerberg Initiative.

- +

diff --git a/images/czi-logo.png b/images/czi-logo.png deleted file mode 100644 index ed780a516bf14c5c9fe6f9f4b3c71bdf8a1022b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51229 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 S?r-1#2uV>{kt!km!2bnu$MNg{ From 6bc6e1901e0c99c46c3fab0da9eef841fab1335c Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 12 May 2023 16:27:24 +0930 Subject: [PATCH 37/76] DOC: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f1d52c..adf6226 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ Vijini Mallawaarachchi, Anuradha Wickramarachchi, Yu Lin. GraphBin: Refined binn ## Funding -GraphBin is funded by a [Essential Open Source Software for Science Grant](https://chanzuckerberg.com/eoss/proposals/cogent3-python-apis-for-iq-tree-and-graphbin-via-a-plug-in-architecture/) from the Chan Zuckerberg Initiative. +GraphBin is funded by an [Essential Open Source Software for Science Grant](https://chanzuckerberg.com/eoss/proposals/cogent3-python-apis-for-iq-tree-and-graphbin-via-a-plug-in-architecture/) from the Chan Zuckerberg Initiative.

From 50554d7f95a628ac4642d4646d212e43134ff698 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 11:22:22 +0930 Subject: [PATCH 38/76] DEV: Convert to use click and fix #37 --- environment.yml | 1 + requirements.txt | 3 +- src/graphbin/__init__.py | 322 +++++++++++-------- src/graphbin/utils/graphbin_Canu.py | 53 +-- src/graphbin/utils/graphbin_Flye.py | 53 +-- src/graphbin/utils/graphbin_Func.py | 1 + src/graphbin/utils/graphbin_MEGAHIT.py | 53 +-- src/graphbin/utils/graphbin_Miniasm.py | 51 +-- src/graphbin/utils/graphbin_Options.py | 50 --- src/graphbin/utils/graphbin_SGA.py | 55 +--- src/graphbin/utils/graphbin_SPAdes.py | 56 +--- src/graphbin/utils/parsers/canu_parser.py | 18 +- src/graphbin/utils/parsers/flye_parser.py | 18 +- src/graphbin/utils/parsers/megahit_parser.py | 18 +- src/graphbin/utils/parsers/miniasm_parser.py | 18 +- src/graphbin/utils/parsers/sga_parser.py | 18 +- src/graphbin/utils/parsers/spades_parser.py | 18 +- 17 files changed, 315 insertions(+), 491 deletions(-) delete mode 100644 src/graphbin/utils/graphbin_Options.py diff --git a/environment.yml b/environment.yml index 9cb066b..c5e3a11 100644 --- a/environment.yml +++ b/environment.yml @@ -9,3 +9,4 @@ dependencies: - cogent3 - cairocffi - python-igraph>=0.7.1 + - click diff --git a/requirements.txt b/requirements.txt index 23fb535..0de4403 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ cogent3 python-igraph>=0.7.1 -cairocffi \ No newline at end of file +cairocffi +click \ No newline at end of file diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index 9545bc6..8480037 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -2,15 +2,17 @@ """graphbin: Refined binning of metagenomic contigs using assembly graphs.""" +import logging import os import sys +import click + from graphbin.utils import ( graphbin_Canu, graphbin_Flye, graphbin_MEGAHIT, graphbin_Miniasm, - graphbin_Options, graphbin_SGA, graphbin_SPAdes, ) @@ -26,162 +28,206 @@ __status__ = "Production" -def run(args): - RUNNER = { - "canu": graphbin_Canu.run, - "flye": graphbin_Flye.run, - "megahit": graphbin_MEGAHIT.run, - "miniasm": graphbin_Miniasm.run, - "sga": graphbin_SGA.run, - "spades": graphbin_SPAdes.run, - } - RUNNER[args.assembler](args) - - -def main(): - parser = graphbin_Options.PARSER - parser.add_argument( - "--assembler", - type=str, - help="name of the assembler used (SPAdes, SGA or MEGAHIT). GraphBin supports Flye, Canu and Miniasm long-read assemblies as well.", - default="", - ) - parser.add_argument( - "--paths", - default=None, - required=False, - help="path to the contigs.paths file, only needed for SPAdes", - ) - parser.add_argument( - "--contigs", - default=None, - help="path to the contigs.fa file.", - ) - parser.add_argument( - "--delimiter", - required=False, - type=str, - default=",", - help="delimiter for input/output results. Supports a comma (,), a semicolon (;), a tab ($'\\t'), a space (\" \") and a pipe (|) [default: , (comma)]", - ) - - args = parser.parse_args() - - if args.version: - print("GraphBin version %s" % __version__) - sys.exit(0) - - # Validation of inputs - # --------------------------------------------------- - # Check assembler type - if len(sys.argv) == 1: - parser.print_help(sys.stderr) - sys.exit(1) - - args.assembler = args.assembler.lower() - - if not ( - args.assembler.lower() == "spades" - or args.assembler.lower() == "sga" - or args.assembler.lower() == "megahit" - or args.assembler.lower() == "flye" - or args.assembler.lower() == "canu" - or args.assembler.lower() == "miniasm" +class ArgsObj: + def __init__( + self, + assembler, + graph, + contigs, + paths, + binned, + output, + prefix, + max_iteration, + diff_threshold, + delimiter, ): - print( - "\nPlease make sure to provide the correct assembler type (SPAdes, SGA or MEGAHIT). GraphBin supports Flye, Canu and Miniasm long-read assemblies as well." - ) + self.assembler = assembler + self.graph = graph + self.contigs = contigs + self.paths = paths + self.binned = binned + self.output = output + self.prefix = prefix + self.max_iteration = max_iteration + self.diff_threshold = diff_threshold + self.delimiter = delimiter + + +@click.command() +@click.option( + "--assembler", + help="name of the assembler used (SPAdes, SGA or MEGAHIT). GraphBin supports Flye, Canu and Miniasm long-read assemblies as well.", + type=click.Choice( + ["spades", "sga", "megahit", "flye", "canu", "miniasm"], case_sensitive=False + ), + required=True, +) +@click.option( + "--graph", + help="path to the assembly graph file", + type=click.Path(exists=True), + required=True, +) +@click.option( + "--contigs", + help="path to the contigs file", + type=click.Path(exists=True), + required=True, +) +@click.option( + "--paths", + help="path to the contigs.paths (metaSPAdes) or assembly.info (metaFlye) file", + type=click.Path(exists=True), + required=False, +) +@click.option( + "--binned", + help="path to the .csv file with the initial binning output from an existing tool", + type=click.Path(exists=True), + required=True, +) +@click.option( + "--output", + help="path to the output folder", + type=click.Path(dir_okay=True, writable=True, readable=True), + required=True, +) +@click.option( + "--prefix", + help="prefix for the output file", + type=str, + required=False, +) +@click.option( + "--max_iteration", + help="maximum number of iterations for label propagation algorithm", + type=int, + default=100, + show_default=True, + required=False, +) +@click.option( + "--diff_threshold", + help="difference threshold for label propagation algorithm", + type=click.FloatRange(0, 1), + default=0.1, + show_default=True, + required=False, +) +@click.option( + "--delimiter", + help="delimiter for input/output results. Supports a comma (,), a semicolon (;), a tab ($'\\t'), a space (\" \") and a pipe (|)", + type=click.Choice([",", ";", "$'\\t'", '" "'], case_sensitive=False), + default=",", + show_default=True, + required=False, +) +@click.version_option(__version__, "-v", "--version", is_flag=True) +def main( + assembler, + graph, + contigs, + paths, + binned, + output, + prefix, + max_iteration, + diff_threshold, + delimiter, +): + """ + GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs + """ + + # Setup logger + # --------------------------------------------------- - print("\nExiting GraphBin...\nBye...!\n") - sys.exit(1) + logger = logging.getLogger("GraphBin %s" % __version__) + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") + consoleHeader = logging.StreamHandler() + consoleHeader.setFormatter(formatter) + consoleHeader.setLevel(logging.INFO) + logger.addHandler(consoleHeader) - # Check assembly graph file - if not os.path.exists(args.graph): - print("\nFailed to open the assembly graph file.") + fileHandler = logging.FileHandler(f"{output}{prefix}graphbin.log") + fileHandler.setLevel(logging.DEBUG) + fileHandler.setFormatter(formatter) + logger.addHandler(fileHandler) - print("\nExiting GraphBin...\nBye...!\n") - sys.exit(1) + # Validate options + # --------------------------------------------------- # Check if paths files is provided when the assembler type is SPAdes - if args.assembler.lower() == "spades" and args.paths is None: - print("\nPlease make sure to provide the path to the contigs.paths file.") - - print("\nExiting GraphBin...\nBye...!\n") - sys.exit(1) - - # Check contigs.paths file for SPAdes - if args.assembler.lower() == "spades" and not os.path.exists(args.paths): - print("\nFailed to open the contigs.paths file.") - - print("\nExiting GraphBin...\nBye...!\n") - sys.exit(1) - - # Check if contigs.fa files is provided - if args.contigs is None: - print("\nPlease make sure to provide the path to the contigs file.") - - print("\nExiting GraphBin...\nBye...!\n") - sys.exit(1) - - # Check contigs file - if not os.path.exists(args.contigs): - print("\nFailed to open the contigs file.") - - print("\nExiting GraphBin...\nBye...!\n") + if assembler.lower() == "spades" and paths is None: + logger.error("Please make sure to provide the path to the contigs.paths file.") + logger.info("Exiting GraphBin... Bye...!") sys.exit(1) - # Check the file with the initial binning output - if not os.path.exists(args.binned): - print("\nFailed to open the file with the initial binning output.") - - print("\nExiting GraphBin...\nBye...!\n") - sys.exit(1) - - # Handle for missing trailing forwardslash in output folder path - if args.output[-1:] != "/": - args.output = args.output + "/" - - # Create output folder if it does not exist - os.makedirs(args.output, exist_ok=True) - # Validate prefix - if args.prefix != "": - if not args.prefix.endswith("_"): - args.prefix = args.prefix + "_" - - # Validate delimiter - delimiters = [",", ";", " ", "\t", "|"] - - if args.delimiter not in delimiters: - print("\nPlease enter a valid delimiter") - print("Exiting GraphBin...\nBye...!\n") - sys.exit(1) + if prefix != None: + if not prefix.endswith("_"): + prefix = prefix + "_" + else: + prefix = "" # Validate max_iteration - if args.max_iteration <= 0: - print("\nPlease enter a valid number for max_iteration") - - print("\nExiting GraphBin...\nBye...!\n") + if max_iteration <= 0: + logger.error("Please enter a valid number for max_iteration") + logger.info("Exiting GraphBin... Bye...!") sys.exit(1) # Validate diff_threshold - if args.diff_threshold < 0: - print("\nPlease enter a valid number for diff_threshold") - - print("\nExiting GraphBin...\nBye...!\n") + if diff_threshold < 0: + logger.error("Please enter a valid number for diff_threshold") + logger.info("Exiting GraphBin... Bye...!") sys.exit(1) - # Remove previous files if they exist - if os.path.exists(args.output + args.prefix + "graphbin.log"): - os.remove(args.output + args.prefix + "graphbin.log") - if os.path.exists(args.output + args.prefix + "graphbin_output.csv"): - os.remove(args.output + args.prefix + "graphbin_output.csv") - if os.path.exists(args.output + args.prefix + "graphbin_unbinned.csv"): - os.remove(args.output + args.prefix + "graphbin_unbinned.csv") + # # Remove previous files if they exist + # if os.path.exists(f"{output}{prefix}graphbin.log"): + # os.remove(f"{output}{prefix}graphbin.log") + # if os.path.exists(f"{output}{prefix}graphbin_output.csv"): + # os.remove(f"{output}{prefix}graphbin_output.csv") + # if os.path.exists(f"{output}{prefix}graphbin_unbinned.csv"): + # os.remove(f"{output}{prefix}graphbin_unbinned.csv") + + # Make args object + args = ArgsObj( + assembler, + graph, + contigs, + paths, + binned, + output, + prefix, + max_iteration, + diff_threshold, + delimiter, + ) # Run GraphBin # --------------------------------------------------- - run(args) + if assembler.lower() == "canu": + graphbin_Canu.main(args) + if assembler.lower() == "flye": + graphbin_Flye.main(args) + if assembler.lower() == "megahit": + graphbin_MEGAHIT.main(args) + if assembler.lower() == "miniasm": + graphbin_Miniasm.main(args) + if assembler.lower() == "sga": + graphbin_SGA.main(args) + if assembler.lower() == "spades": + graphbin_SPAdes.main(args) + + # Exit program + # -------------- + + logger.info("Thank you for using GraphBin! Bye...!") + + logger.removeHandler(fileHandler) + logger.removeHandler(consoleHeader) if __name__ == "__main__": diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/utils/graphbin_Canu.py index c6c5724..005bc4e 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/utils/graphbin_Canu.py @@ -15,11 +15,10 @@ import time from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.parsers import get_initial_bin_count from graphbin.utils.parsers.canu_parser import ( - parse_graph, get_initial_binning_result, + parse_graph, write_output, ) @@ -33,19 +32,11 @@ __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" +# create logger +logger = logging.getLogger("GraphBin %s" % __version__) -def run(args): - # Setup logger - # ----------------------- - - logger = logging.getLogger("GraphBin %s" % __version__) - logger.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") - consoleHeader = logging.StreamHandler() - consoleHeader.setFormatter(formatter) - consoleHeader.setLevel(logging.INFO) - logger.addHandler(consoleHeader) +def run(args): start_time = time.time() assembly_graph_file = args.graph @@ -58,14 +49,6 @@ def run(args): diff_threshold = args.diff_threshold MIN_BIN_COUNT = 10 - # Setup output path for log file - # --------------------------------------------------- - - fileHandler = logging.FileHandler(output_path + "/" + prefix + "graphbin.log") - fileHandler.setLevel(logging.DEBUG) - fileHandler.setFormatter(formatter) - logger.addHandler(fileHandler) - logger.info( "Welcome to GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs." ) @@ -73,11 +56,11 @@ def run(args): "This version of GraphBin makes use of the assembly graph produced by Canu which is a long reads assembler based on the OLC approach." ) - logger.info("Assembly graph file: " + assembly_graph_file) - logger.info("Existing binning output file: " + contig_bins_file) - logger.info("Final binning output file: " + output_path) - logger.info("Maximum number of iterations: " + str(max_iteration)) - logger.info("Difference threshold: " + str(diff_threshold)) + logger.info(f"Assembly graph file: {assembly_graph_file}") + logger.info(f"Existing binning output file: {contig_bins_file}") + logger.info(f"Final binning output file: {output_path}") + logger.info(f"Maximum number of iterations: {max_iteration}") + logger.info(f"Difference threshold: {diff_threshold}") logger.info("GraphBin started") @@ -88,7 +71,7 @@ def run(args): # Get assembly graph # -------------------- - + assembly_graph, contigs_map, node_count = parse_graph(assembly_graph_file) # Get initial binning result @@ -114,7 +97,7 @@ def run(args): elapsed_time = time.time() - start_time # Print elapsed time for the process - logger.info("Elapsed time: " + str(elapsed_time) + " seconds") + logger.info(f"Elapsed time: {elapsed_time} seconds") # Write result to output file # ----------------------------- @@ -134,20 +117,8 @@ def run(args): non_isolated, ) - # Exit program - # -------------- - - logger.info("Thank you for using GraphBin! Bye...!") - - logger.removeHandler(fileHandler) - logger.removeHandler(consoleHeader) - -def main(): - # Setup argument parser - # ----------------------- - ap = PARSER - args = ap.parse_args() +def main(args): run(args) diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index cf24878..ac71a7d 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -15,11 +15,10 @@ import time from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.parsers import get_initial_bin_count from graphbin.utils.parsers.flye_parser import ( - parse_graph, get_initial_binning_result, + parse_graph, write_output, ) @@ -33,19 +32,11 @@ __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" +# create logger +logger = logging.getLogger("GraphBin %s" % __version__) -def run(args): - # Setup logger - # ----------------------- - - logger = logging.getLogger("GraphBin %s" % __version__) - logger.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") - consoleHeader = logging.StreamHandler() - consoleHeader.setFormatter(formatter) - consoleHeader.setLevel(logging.INFO) - logger.addHandler(consoleHeader) +def run(args): start_time = time.time() assembly_graph_file = args.graph @@ -58,14 +49,6 @@ def run(args): max_iteration = args.max_iteration diff_threshold = args.diff_threshold - # Setup output path for log file - # --------------------------------------------------- - - fileHandler = logging.FileHandler(output_path + "/" + prefix + "graphbin.log") - fileHandler.setLevel(logging.DEBUG) - fileHandler.setFormatter(formatter) - logger.addHandler(fileHandler) - logger.info( "Welcome to GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs." ) @@ -73,12 +56,12 @@ def run(args): "This version of GraphBin makes use of the assembly graph produced by Flye which is a long reads assembler based on the de Bruijn graph approach." ) - logger.info("Assembly graph file: " + assembly_graph_file) - logger.info("Existing binning output file: " + contig_bins_file) - logger.info("Contig paths file: " + contig_paths) - logger.info("Final binning output file: " + output_path) - logger.info("Maximum number of iterations: " + str(max_iteration)) - logger.info("Difference threshold: " + str(diff_threshold)) + logger.info(f"Assembly graph file: {assembly_graph_file}") + logger.info(f"Existing binning output file: {contig_bins_file}") + logger.info(f"Contig paths file: {contig_paths}") + logger.info(f"Final binning output file: {output_path}") + logger.info(f"Maximum number of iterations: {max_iteration}") + logger.info(f"Difference threshold: {diff_threshold}") logger.info("GraphBin started") @@ -117,7 +100,7 @@ def run(args): elapsed_time = time.time() - start_time # Print elapsed time for the process - logger.info("Elapsed time: " + str(elapsed_time) + " seconds") + logger.info(f"Elapsed time: {elapsed_time} seconds") # Write result to output file # ----------------------------- @@ -138,20 +121,8 @@ def run(args): ) logger.info("Writing the Final Binning result to file") - # Exit program - # -------------- - - logger.info("Thank you for using GraphBin! Bye...!") - - logger.removeHandler(fileHandler) - logger.removeHandler(consoleHeader) - -def main(): - # Setup argument parser - # ----------------------- - ap = PARSER - args = ap.parse_args() +def main(args): run(args) diff --git a/src/graphbin/utils/graphbin_Func.py b/src/graphbin/utils/graphbin_Func.py index 468a4ca..f0210dd 100644 --- a/src/graphbin/utils/graphbin_Func.py +++ b/src/graphbin/utils/graphbin_Func.py @@ -5,6 +5,7 @@ from graphbin.utils.labelpropagation.labelprop import LabelProp + __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/utils/graphbin_MEGAHIT.py index 374c965..7eb70cd 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/utils/graphbin_MEGAHIT.py @@ -15,12 +15,11 @@ import time from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.parsers import get_initial_bin_count from graphbin.utils.parsers.megahit_parser import ( - parse_graph, - get_initial_binning_result, get_contig_descriptors, + get_initial_binning_result, + parse_graph, write_output, ) @@ -34,19 +33,11 @@ __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" +# create logger +logger = logging.getLogger("GraphBin %s" % __version__) -def run(args): - # Setup logger - # ----------------------- - - logger = logging.getLogger("GraphBin %s" % __version__) - logger.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") - consoleHeader = logging.StreamHandler() - consoleHeader.setFormatter(formatter) - consoleHeader.setLevel(logging.INFO) - logger.addHandler(consoleHeader) +def run(args): start_time = time.time() assembly_graph_file = args.graph @@ -59,14 +50,6 @@ def run(args): diff_threshold = args.diff_threshold MIN_BIN_COUNT = 10 - # Setup output path for log file - # --------------------------------------------------- - - fileHandler = logging.FileHandler(output_path + "/" + prefix + "graphbin.log") - fileHandler.setLevel(logging.DEBUG) - fileHandler.setFormatter(formatter) - logger.addHandler(fileHandler) - logger.info( "Welcome to GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs." ) @@ -74,11 +57,11 @@ def run(args): "This version of GraphBin makes use of the assembly graph produced by MEGAHIT which is based on the de Bruijn graph approach." ) - logger.info("Assembly graph file: " + assembly_graph_file) - logger.info("Existing binning output file: " + contig_bins_file) - logger.info("Final binning output file: " + output_path) - logger.info("Maximum number of iterations: " + str(max_iteration)) - logger.info("Difference threshold: " + str(diff_threshold)) + logger.info(f"Assembly graph file: {assembly_graph_file}") + logger.info(f"Existing binning output file: {contig_bins_file}") + logger.info(f"Final binning output file: {output_path}") + logger.info(f"Maximum number of iterations: {max_iteration}") + logger.info(f"Difference threshold: {diff_threshold}") logger.info("GraphBin started") @@ -127,7 +110,7 @@ def run(args): elapsed_time = time.time() - start_time # Print elapsed time for the process - logger.info("Elapsed time: " + str(elapsed_time) + " seconds") + logger.info(f"Elapsed time: {elapsed_time} seconds") # Write result to output file # ----------------------------- @@ -147,20 +130,8 @@ def run(args): non_isolated, ) - # Exit program - # -------------- - - logger.info("Thank you for using GraphBin! Bye...!") - - logger.removeHandler(fileHandler) - logger.removeHandler(consoleHeader) - -def main(): - # Setup argument parser - # ----------------------- - ap = PARSER - args = ap.parse_args() +def main(args): run(args) diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/utils/graphbin_Miniasm.py index 1e69b26..b09887d 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/utils/graphbin_Miniasm.py @@ -15,11 +15,10 @@ import time from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.parsers import get_initial_bin_count from graphbin.utils.parsers.miniasm_parser import ( - parse_graph, get_initial_binning_result, + parse_graph, write_output, ) @@ -33,19 +32,11 @@ __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" +# create logger +logger = logging.getLogger("GraphBin %s" % __version__) -def run(args): - # Setup logger - # ----------------------- - - logger = logging.getLogger("GraphBin %s" % __version__) - logger.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") - consoleHeader = logging.StreamHandler() - consoleHeader.setFormatter(formatter) - consoleHeader.setLevel(logging.INFO) - logger.addHandler(consoleHeader) +def run(args): start_time = time.time() assembly_graph_file = args.graph @@ -58,14 +49,6 @@ def run(args): diff_threshold = args.diff_threshold MIN_BIN_COUNT = 10 - # Setup output path for log file - # --------------------------------------------------- - - fileHandler = logging.FileHandler(output_path + "/" + prefix + "graphbin.log") - fileHandler.setLevel(logging.INFO) - fileHandler.setFormatter(formatter) - logger.addHandler(fileHandler) - logger.info( "Welcome to GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs." ) @@ -73,11 +56,11 @@ def run(args): "This version of GraphBin makes use of the assembly graph produced by Miniasm." ) - logger.info("Assembly graph file: " + assembly_graph_file) - logger.info("Existing binning output file: " + contig_bins_file) - logger.info("Final binning output file: " + output_path) - logger.info("Maximum number of iterations: " + str(max_iteration)) - logger.info("Difference threshold: " + str(diff_threshold)) + logger.info(f"Assembly graph file: {assembly_graph_file}") + logger.info(f"Existing binning output file: {contig_bins_file}") + logger.info(f"Final binning output file: {output_path}") + logger.info(f"Maximum number of iterations: {max_iteration}") + logger.info(f"Difference threshold: {diff_threshold}") logger.info("GraphBin started") @@ -114,7 +97,7 @@ def run(args): elapsed_time = time.time() - start_time # Print elapsed time for the process - logger.info("Elapsed time: " + str(elapsed_time) + " seconds") + logger.info(f"Elapsed time: {elapsed_time} seconds") # Write result to output file # ----------------------------- @@ -133,20 +116,8 @@ def run(args): non_isolated, ) - # Exit program - # -------------- - - logger.info("Thank you for using GraphBin! Bye...!") - - logger.removeHandler(fileHandler) - logger.removeHandler(consoleHeader) - -def main(): - # Setup argument parser - # ----------------------- - ap = PARSER - args = ap.parse_args() +def main(args): run(args) diff --git a/src/graphbin/utils/graphbin_Options.py b/src/graphbin/utils/graphbin_Options.py deleted file mode 100644 index d43a795..0000000 --- a/src/graphbin/utils/graphbin_Options.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 - -import argparse - - -__author__ = "Vijini Mallawaarachchi" -__copyright__ = "Copyright 2019-2022, GraphBin Project" -__credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] -__license__ = "BSD-3" -__version__ = "1.6.1" -__maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" -__status__ = "Production" - - -PARSER = argparse.ArgumentParser( - description="""GraphBin Help. GraphBin is a metagenomic contig binning tool - that makes use of the contig connectivity information from the assembly graph to bin contigs. It utilizes the - binning result of an existing binning tool and a label propagation algorithm to correct mis-binned contigs - and predict the labels of contigs which are discarded due to short length.""", - prog="graphbin", -) - -PARSER.add_argument("--version", default=False, action="store_true") - -PARSER.add_argument("--graph", type=str, help="path to the assembly graph file") - -PARSER.add_argument( - "--binned", - type=str, - help="path to the .csv file with the initial binning output from an existing tool", -) - -PARSER.add_argument("--output", type=str, help="path to the output folder") - -PARSER.add_argument("--prefix", type=str, default="", help="prefix for the output file") - -PARSER.add_argument( - "--max_iteration", - type=int, - default=100, - help="maximum number of iterations for label propagation algorithm. [default: 100]", -) - -PARSER.add_argument( - "--diff_threshold", - type=float, - default=0.1, - help="difference threshold for label propagation algorithm. [default: 0.1]", -) diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/utils/graphbin_SGA.py index 7e7670f..6362be0 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/utils/graphbin_SGA.py @@ -15,12 +15,11 @@ import time from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.parsers import get_initial_bin_count from graphbin.utils.parsers.sga_parser import ( - parse_graph, - get_initial_binning_result, get_contig_descriptions, + get_initial_binning_result, + parse_graph, write_output, ) @@ -34,19 +33,11 @@ __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" +# create logger +logger = logging.getLogger("GraphBin %s" % __version__) -def run(args): - # Setup logger - # ----------------------- - - logger = logging.getLogger("GraphBin %s" % __version__) - logger.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") - consoleHeader = logging.StreamHandler() - consoleHeader.setFormatter(formatter) - consoleHeader.setLevel(logging.INFO) - logger.addHandler(consoleHeader) +def run(args): start_time = time.time() assembly_graph_file = args.graph @@ -58,14 +49,6 @@ def run(args): max_iteration = args.max_iteration diff_threshold = args.diff_threshold - # Setup output path for log file - # --------------------------------------------------- - - fileHandler = logging.FileHandler(output_path + "/" + prefix + "graphbin.log") - fileHandler.setLevel(logging.DEBUG) - fileHandler.setFormatter(formatter) - logger.addHandler(fileHandler) - logger.info( "Welcome to GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs." ) @@ -73,11 +56,11 @@ def run(args): "This version of GraphBin makes use of the assembly graph produced by SGA which is based on the OLC (more recent string graph) approach." ) - logger.info("Assembly graph file: " + assembly_graph_file) - logger.info("Existing binning output file: " + contig_bins_file) - logger.info("Final binning output file: " + output_path) - logger.info("Maximum number of iterations: " + str(max_iteration)) - logger.info("Difference threshold: " + str(diff_threshold)) + logger.info(f"Assembly graph file: {assembly_graph_file}") + logger.info(f"Existing binning output file: {contig_bins_file}") + logger.info(f"Final binning output file: {output_path}") + logger.info(f"Maximum number of iterations: {max_iteration}") + logger.info(f"Difference threshold: {diff_threshold}") logger.info("GraphBin started") @@ -104,7 +87,7 @@ def run(args): # Run GraphBin logic # ------------------------------------- - + final_bins, remove_labels, non_isolated = graphbin_main( n_bins, bins, @@ -118,7 +101,7 @@ def run(args): elapsed_time = time.time() - start_time # Print elapsed time for the process - logger.info("Elapsed time: " + str(elapsed_time) + " seconds") + logger.info(f"Elapsed time: {elapsed_time} seconds") # Write result to output file # ----------------------------- @@ -139,20 +122,8 @@ def run(args): contig_descriptions, ) - # Exit program - # -------------- - - logger.info("Thank you for using GraphBin! Bye...!") - - logger.removeHandler(fileHandler) - logger.removeHandler(consoleHeader) - -def main(): - # Setup argument parser - # ----------------------- - ap = PARSER - args = ap.parse_args() +def main(args): run(args) diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/utils/graphbin_SPAdes.py index beb887b..dca186c 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/utils/graphbin_SPAdes.py @@ -15,11 +15,10 @@ import time from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.graphbin_Options import PARSER from graphbin.utils.parsers import get_initial_bin_count from graphbin.utils.parsers.spades_parser import ( - parse_graph, get_initial_binning_result, + parse_graph, write_output, ) @@ -33,19 +32,11 @@ __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" +# create logger +logger = logging.getLogger("GraphBin %s" % __version__) -def run(args): - # Setup logger - # ----------------------- - - logger = logging.getLogger("GraphBin %s" % __version__) - logger.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") - consoleHeader = logging.StreamHandler() - consoleHeader.setFormatter(formatter) - consoleHeader.setLevel(logging.INFO) - logger.addHandler(consoleHeader) +def run(args): start_time = time.time() assembly_graph_file = args.graph @@ -58,14 +49,6 @@ def run(args): max_iteration = args.max_iteration diff_threshold = args.diff_threshold - # Setup output path for log file - # --------------------------------------------------- - - fileHandler = logging.FileHandler(output_path + "/" + prefix + "graphbin.log") - fileHandler.setLevel(logging.DEBUG) - fileHandler.setFormatter(formatter) - logger.addHandler(fileHandler) - logger.info( "Welcome to GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs." ) @@ -74,12 +57,12 @@ def run(args): ) logger.info("Input arguments:") - logger.info("Assembly graph file: " + assembly_graph_file) - logger.info("Contig paths file: " + contig_paths) - logger.info("Existing binning output file: " + contig_bins_file) - logger.info("Final binning output file: " + output_path) - logger.info("Maximum number of iterations: " + str(max_iteration)) - logger.info("Difference threshold: " + str(diff_threshold)) + logger.info(f"Assembly graph file: {assembly_graph_file}") + logger.info(f"Contig paths file: {contig_paths}") + logger.info(f"Existing binning output file: {contig_bins_file}") + logger.info(f"Final binning output file: {output_path}") + logger.info(f"Maximum number of iterations: {max_iteration}") + logger.info(f"Difference threshold: {diff_threshold}") logger.info("GraphBin started") @@ -104,7 +87,7 @@ def run(args): # Run GraphBin logic # ------------------------------------- - + final_bins, remove_labels, non_isolated = graphbin_main( n_bins, bins, @@ -118,7 +101,7 @@ def run(args): elapsed_time = time.time() - start_time # Print elapsed time for the process - logger.info("Elapsed time: " + str(elapsed_time) + " seconds") + logger.info(f"Elapsed time: {elapsed_time} seconds") # Write result to output file # ----------------------------- @@ -138,21 +121,8 @@ def run(args): non_isolated, ) - # Exit program - # -------------- - - logger.info("Thank you for using GraphBin! Bye...!") - logger.removeHandler(fileHandler) - logger.removeHandler(consoleHeader) - - -def main(): - # Setup argument parser - # --------------------------------------------------- - ap = PARSER - ap.add_argument("--paths", type=str, help="path to the contigs.paths file") - args = ap.parse_args() +def main(args): run(args) diff --git a/src/graphbin/utils/parsers/canu_parser.py b/src/graphbin/utils/parsers/canu_parser.py index 6ccc8bc..c14895e 100644 --- a/src/graphbin/utils/parsers/canu_parser.py +++ b/src/graphbin/utils/parsers/canu_parser.py @@ -101,7 +101,7 @@ def parse_graph(assembly_graph_file): contigs_map = my_map contigs_map_rev = my_map.inverse - logger.info("Total number of contigs available: " + str(node_count)) + logger.info(f"Total number of contigs available: {node_count}") ## Construct the assembly graph # ------------------------------- @@ -140,7 +140,7 @@ def parse_graph(assembly_graph_file): logger.info("Exiting GraphBin... Bye...!") sys.exit(1) - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + logger.info(f"Total number of edges in the assembly graph: {len(edge_list)}") return assembly_graph, contigs_map, node_count @@ -163,17 +163,17 @@ def write_output( output_bins = [] - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" + output_bins_path = f"{output_path}{prefix}bins/" + output_file = f"{output_path}{prefix}graphbin_output.csv" if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) + subprocess.run(f"mkdir -p {output_bins_path}", shell=True) bin_files = {} for bin_name in set(final_bins.values()): bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + f"{output_bins_path}{prefix}bin_{bin_name}.fasta", "w+" ) for label, seq in MinimalFastaParser( @@ -202,7 +202,7 @@ def write_output( for row in output_bins: output_writer.writerow(row) - logger.info("Final binning results can be found in " + str(output_bins_path)) + logger.info(f"Final binning results can be found in {output_bins_path}") unbinned_contigs = [] @@ -213,7 +213,7 @@ def write_output( unbinned_contigs.append(line) if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + unbinned_file = f"{output_path}{prefix}graphbin_unbinned.csv" with open(unbinned_file, mode="w") as out_file: output_writer = csv.writer( @@ -223,4 +223,4 @@ def write_output( for row in unbinned_contigs: output_writer.writerow(row) - logger.info("Unbinned contigs can be found at " + unbinned_file) + logger.info(f"Unbinned contigs can be found at {unbinned_file}") diff --git a/src/graphbin/utils/parsers/flye_parser.py b/src/graphbin/utils/parsers/flye_parser.py index b5ea4c0..8076935 100644 --- a/src/graphbin/utils/parsers/flye_parser.py +++ b/src/graphbin/utils/parsers/flye_parser.py @@ -202,7 +202,7 @@ def parse_graph(assembly_graph_file, contig_paths): logger.info("Exiting GraphBin... Bye...!") sys.exit(1) - logger.info("Total number of contigs available: " + str(node_count)) + logger.info(f"Total number of contigs available: {node_count}") ## Construct the assembly graph # ------------------------------- @@ -231,7 +231,7 @@ def parse_graph(assembly_graph_file, contig_paths): logger.info("Exiting GraphBin... Bye...!") sys.exit(1) - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + logger.info(f"Total number of edges in the assembly graph: {len(edge_list)}") return assembly_graph, contig_names, node_count @@ -254,17 +254,17 @@ def write_output( output_bins = [] - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" + output_bins_path = f"{output_path}{prefix}bins/" + output_file = f"{output_path}{prefix}graphbin_output.csv" if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) + subprocess.run(f"mkdir -p {output_bins_path}", shell=True) bin_files = {} for bin_name in set(final_bins.values()): bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + f"{output_bins_path}{prefix}bin_{bin_name}.fasta", "w+" ) for label, seq in MinimalFastaParser(contigs_file): @@ -295,7 +295,7 @@ def write_output( for row in output_bins: output_writer.writerow(row) - logger.info("Final binning results can be found in " + str(output_bins_path)) + logger.info(f"Final binning results can be found in {output_bins_path}") unbinned_contigs = [] @@ -306,7 +306,7 @@ def write_output( unbinned_contigs.append(line) if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + unbinned_file = f"{output_path}{prefix}graphbin_unbinned.csv" with open(unbinned_file, mode="w") as out_file: output_writer = csv.writer( @@ -316,4 +316,4 @@ def write_output( for row in unbinned_contigs: output_writer.writerow(row) - logger.info("Unbinned contigs can be found at " + unbinned_file) + logger.info(f"Unbinned contigs can be found at {unbinned_file}") diff --git a/src/graphbin/utils/parsers/megahit_parser.py b/src/graphbin/utils/parsers/megahit_parser.py index 83e7a2c..769f325 100644 --- a/src/graphbin/utils/parsers/megahit_parser.py +++ b/src/graphbin/utils/parsers/megahit_parser.py @@ -113,7 +113,7 @@ def parse_graph(assembly_graph_file, original_contigs): node_count += 1 - logger.info("Total number of contigs available: " + str(node_count)) + logger.info(f"Total number of contigs available: {node_count}") contigs_map = my_map contigs_map_rev = my_map.inverse @@ -150,7 +150,7 @@ def parse_graph(assembly_graph_file, original_contigs): logger.info("Exiting GraphBin... Bye...!") sys.exit(1) - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + logger.info(f"Total number of edges in the assembly graph: {len(edge_list)}") # Map original contig IDs to contig IDS of assembly graph # -------------------------------------------------------- @@ -184,17 +184,17 @@ def write_output( graph_to_contig_map_rev = graph_to_contig_map.inverse contigs_map_rev = contigs_map.inverse - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" + output_bins_path = f"{output_path}{prefix}bins/" + output_file = f"{output_path}{prefix}graphbin_output.csv" if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) + subprocess.run(f"mkdir -p {output_bins_path}", shell=True) bin_files = {} for bin_name in set(final_bins.values()): bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + f"{output_bins_path}{prefix}bin_{bin_name}.fasta", "w+" ) for label, seq in MinimalFastaParser( @@ -223,7 +223,7 @@ def write_output( for row in output_bins: output_writer.writerow(row) - logger.info("Final binning results can be found in " + str(output_bins_path)) + logger.info(f"Final binning results can be found in {output_bins_path}") unbinned_contigs = [] @@ -234,7 +234,7 @@ def write_output( unbinned_contigs.append(line) if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + unbinned_file = f"{output_path}{prefix}graphbin_unbinned.csv" with open(unbinned_file, mode="w") as out_file: output_writer = csv.writer( @@ -244,7 +244,7 @@ def write_output( for row in unbinned_contigs: output_writer.writerow(row) - logger.info("Unbinned contigs can be found at " + unbinned_file) + logger.info(f"Unbinned contigs can be found at {unbinned_file}") def get_contig_descriptors(contigs_file): diff --git a/src/graphbin/utils/parsers/miniasm_parser.py b/src/graphbin/utils/parsers/miniasm_parser.py index 92a59df..b3d5c1a 100644 --- a/src/graphbin/utils/parsers/miniasm_parser.py +++ b/src/graphbin/utils/parsers/miniasm_parser.py @@ -101,7 +101,7 @@ def parse_graph(assembly_graph_file): contigs_map = my_map contigs_map_rev = my_map.inverse - logger.info("Total number of contigs available: " + str(node_count)) + logger.info(f"Total number of contigs available: {node_count}") ## Construct the assembly graph # ------------------------------- @@ -140,7 +140,7 @@ def parse_graph(assembly_graph_file): logger.info("Exiting GraphBin... Bye...!") sys.exit(1) - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + logger.info(f"Total number of edges in the assembly graph: {len(edge_list)}") return assembly_graph, contigs_map, node_count @@ -163,17 +163,17 @@ def write_output( output_bins = [] contigs_map_rev = contigs_map.inverse - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" + output_bins_path = f"{output_path}{prefix}bins/" + output_file = f"{output_path}{prefix}graphbin_output.csv" if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) + subprocess.run(f"mkdir -p {output_bins_path}", shell=True) bin_files = {} for bin_name in set(final_bins.values()): bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + f"{output_bins_path}{prefix}bin_{bin_name}.fasta", "w+" ) for label, seq in MinimalFastaParser(contigs_file): @@ -200,7 +200,7 @@ def write_output( for row in output_bins: output_writer.writerow(row) - logger.info("Final binning results can be found in " + str(output_bins_path)) + logger.info(f"Final binning results can be found in {output_bins_path}") unbinned_contigs = [] @@ -211,7 +211,7 @@ def write_output( unbinned_contigs.append(line) if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + unbinned_file = f"{output_path}{prefix}graphbin_unbinned.csv" with open(unbinned_file, mode="w") as out_file: output_writer = csv.writer( @@ -221,4 +221,4 @@ def write_output( for row in unbinned_contigs: output_writer.writerow(row) - logger.info("Unbinned contigs can be found at " + unbinned_file) + logger.info(f"Unbinned contigs can be found at {unbinned_file}") diff --git a/src/graphbin/utils/parsers/sga_parser.py b/src/graphbin/utils/parsers/sga_parser.py index c84dfde..78f3147 100644 --- a/src/graphbin/utils/parsers/sga_parser.py +++ b/src/graphbin/utils/parsers/sga_parser.py @@ -105,7 +105,7 @@ def parse_graph(assembly_graph_file): contig_names_rev = contig_names.inverse - logger.info("Total number of contigs available: " + str(node_count)) + logger.info(f"Total number of contigs available: {node_count}") try: # Create the graph @@ -141,7 +141,7 @@ def parse_graph(assembly_graph_file): logger.info("Exiting GraphBin... Bye...!") sys.exit(1) - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + logger.info(f"Total number of edges in the assembly graph: {len(edge_list)}") return assembly_graph, contigs_map, contig_names, node_count @@ -165,17 +165,17 @@ def write_output( output_bins = [] - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" + output_bins_path = f"{output_path}{prefix}bins/" + output_file = f"{output_path}{prefix}graphbin_output.csv" if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) + subprocess.run(f"mkdir -p {output_bins_path}", shell=True) bin_files = {} for bin_name in set(final_bins.values()): bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + f"{output_bins_path}{prefix}bin_{bin_name}.fasta", "w+" ) for label, seq in MinimalFastaParser( @@ -204,7 +204,7 @@ def write_output( for row in output_bins: output_writer.writerow(row) - logger.info("Final binning results can be found in " + str(output_bins_path)) + logger.info(f"Final binning results can be found in {output_bins_path}") unbinned_contigs = [] @@ -215,7 +215,7 @@ def write_output( unbinned_contigs.append(line) if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + unbinned_file = f"{output_path}{prefix}graphbin_unbinned.csv" with open(unbinned_file, mode="w") as out_file: output_writer = csv.writer( @@ -225,7 +225,7 @@ def write_output( for row in unbinned_contigs: output_writer.writerow(row) - logger.info("Unbinned contigs can be found at " + unbinned_file) + logger.info(f"Unbinned contigs can be found at {unbinned_file}") def get_contig_descriptions(contigs_file): diff --git a/src/graphbin/utils/parsers/spades_parser.py b/src/graphbin/utils/parsers/spades_parser.py index d6808ba..4a67635 100644 --- a/src/graphbin/utils/parsers/spades_parser.py +++ b/src/graphbin/utils/parsers/spades_parser.py @@ -116,7 +116,7 @@ def parse_graph(assembly_graph_file, contig_paths): contigs_map = my_map contigs_map_rev = my_map.inverse - logger.info("Total number of contigs available: " + str(node_count)) + logger.info(f"Total number of contigs available: {node_count}") links = [] links_map = defaultdict(set) @@ -203,7 +203,7 @@ def parse_graph(assembly_graph_file, contig_paths): logger.info("Exiting GraphBin... Bye...!") sys.exit(1) - logger.info("Total number of edges in the assembly graph: " + str(len(edge_list))) + logger.info(f"Total number of edges in the assembly graph: {len(edge_list)}") return assembly_graph, contigs_map, contig_names, node_count @@ -226,17 +226,17 @@ def write_output( output_bins = [] - output_bins_path = output_path + prefix + "bins/" - output_file = output_path + prefix + "graphbin_output.csv" + output_bins_path = f"{output_path}{prefix}bins/" + output_file = f"{output_path}{prefix}graphbin_output.csv" if not os.path.isdir(output_bins_path): - subprocess.run("mkdir -p " + output_bins_path, shell=True) + subprocess.run(f"mkdir -p {output_bins_path}", shell=True) bin_files = {} for bin_name in set(final_bins.values()): bin_files[bin_name] = open( - output_bins_path + prefix + "bin_" + bin_name + ".fasta", "w+" + f"{output_bins_path}{prefix}bin_{bin_name}.fasta", "w+" ) for label, seq in MinimalFastaParser(contigs_file): @@ -263,7 +263,7 @@ def write_output( for row in output_bins: output_writer.writerow(row) - logger.info("Final binning results can be found in " + str(output_bins_path)) + logger.info(f"Final binning results can be found in {output_bins_path}") unbinned_contigs = [] @@ -274,7 +274,7 @@ def write_output( unbinned_contigs.append(line) if len(unbinned_contigs) != 0: - unbinned_file = output_path + prefix + "graphbin_unbinned.csv" + unbinned_file = f"{output_path}{prefix}graphbin_unbinned.csv" with open(unbinned_file, mode="w") as out_file: output_writer = csv.writer( @@ -284,4 +284,4 @@ def write_output( for row in unbinned_contigs: output_writer.writerow(row) - logger.info("Unbinned contigs can be found at " + unbinned_file) + logger.info(f"Unbinned contigs can be found at {unbinned_file}") From 62c9a0e669e6901c84398dbafdf46b67c608e21d Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 11:26:41 +0930 Subject: [PATCH 39/76] DEV: add click to pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b88cde5..515954b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ keywords = ["genomics", "bioinformatics"] readme = "README.md" license = { file = "LICENSE" } requires-python = ">=3.7" -dependencies = ["python-igraph", "cogent3", "cairocffi"] +dependencies = ["python-igraph", "cogent3", "cairocffi", "click"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Science/Research", From 81bb890683af86ee9eb285b3c1882b7ad2a6eb5f Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 11:55:30 +0930 Subject: [PATCH 40/76] DOC: Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index adf6226..3a4b13d 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ GraphBin installation requires python 3 to run. The following dependencies are r * [python-igraph](https://igraph.org/python/) * [cogent3](https://cogent3.org/) * [cairocffi](https://pypi.org/project/cairocffi/) +* [click](https://click.palletsprojects.com/) ## Installing GraphBin From bc1528656768e514c65ecf3068f25966f3a73078 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 12:42:57 +0930 Subject: [PATCH 41/76] TST: update python versions --- .github/workflows/testing_python_app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing_python_app.yml b/.github/workflows/testing_python_app.yml index 29aa06a..963b3b7 100644 --- a/.github/workflows/testing_python_app.yml +++ b/.github/workflows/testing_python_app.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: os: [macos-12, ubuntu-latest] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: "actions/checkout@v3" From 18cb8797290af2ba32bf64654bc6ac9c6adb142d Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 12:43:24 +0930 Subject: [PATCH 42/76] TST: add testing with conda --- .github/workflows/testing_python_conda.yml | 52 ++++++++++++++++++++++ environment.yml | 10 ++--- 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/testing_python_conda.yml diff --git a/.github/workflows/testing_python_conda.yml b/.github/workflows/testing_python_conda.yml new file mode 100644 index 0000000..c6dcf63 --- /dev/null +++ b/.github/workflows/testing_python_conda.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + + +jobs: + tests: + name: "Python ${{ matrix.python-version }}" + runs-on: ${{ matrix.os }} + + defaults: + run: + shell: bash -el {0} + + strategy: + matrix: + os: [macos-12, ubuntu-latest] + python-version: ["3.8", "3.9", "3.10", "3.11"] + + steps: + - uses: "actions/checkout@v3" + with: + fetch-depth: 0 + + # Setup conda env + - uses: conda-incubator/setup-miniconda@v2 + with: + activate-environment: graphbin + environment-file: environment.yml + python-version: ${{ matrix.python-version }} + auto-activate-base: false + + - name: "Setup graphbin on ${{ matrix.os }} for Python ${{ matrix.python-version }}" + run: | + python -m pip install --upgrade pip + pip install . + + - name: "Generate coverage report on ${{ matrix.os }} for Python ${{ matrix.python-version }}" + run: | + pip install pytest pytest-cov + pytest --cov=graphbin --cov-report=xml --cov-append + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: coverage.xml + fail_ci_if_error: true \ No newline at end of file diff --git a/environment.yml b/environment.yml index c5e3a11..efe6bb7 100644 --- a/environment.yml +++ b/environment.yml @@ -4,9 +4,7 @@ channels: - defaults dependencies: - python>=3.7.1 - - pip - - pip: - - cogent3 - - cairocffi - - python-igraph>=0.7.1 - - click + - cogent3 + - cairocffi + - python-igraph>=0.7.1 + - click From 07b3b0f6af70bda8aa9d2771e69772b8596743da Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 12:49:57 +0930 Subject: [PATCH 43/76] TST: fix tests --- .github/workflows/testing_python_conda.yml | 10 ++-------- environment.yml | 3 ++- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/testing_python_conda.yml b/.github/workflows/testing_python_conda.yml index c6dcf63..7765237 100644 --- a/.github/workflows/testing_python_conda.yml +++ b/.github/workflows/testing_python_conda.yml @@ -1,4 +1,4 @@ -name: CI +name: CI conda on: push: @@ -43,10 +43,4 @@ jobs: run: | pip install pytest pytest-cov pytest --cov=graphbin --cov-report=xml --cov-append - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: coverage.xml - fail_ci_if_error: true \ No newline at end of file + \ No newline at end of file diff --git a/environment.yml b/environment.yml index efe6bb7..e019b09 100644 --- a/environment.yml +++ b/environment.yml @@ -1,7 +1,8 @@ name: graphbin channels: - bioconda - - defaults + - conda-forge + - anaconda dependencies: - python>=3.7.1 - cogent3 From 8fe4634c33bd0b4179b6908121f4cbd3d2c93148 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 13:00:07 +0930 Subject: [PATCH 44/76] TST: fix env for tests --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index e019b09..eb08632 100644 --- a/environment.yml +++ b/environment.yml @@ -7,5 +7,5 @@ dependencies: - python>=3.7.1 - cogent3 - cairocffi - - python-igraph>=0.7.1 + - python-igraph - click From a5a31fd1674d5958dd9802316a32a6fe4fcba2f8 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 13:00:26 +0930 Subject: [PATCH 45/76] REL: Update version number --- src/graphbin/__init__.py | 2 +- src/graphbin/utils/graphbin_Canu.py | 2 +- src/graphbin/utils/graphbin_Flye.py | 2 +- src/graphbin/utils/graphbin_Func.py | 2 +- src/graphbin/utils/graphbin_MEGAHIT.py | 2 +- src/graphbin/utils/graphbin_Miniasm.py | 2 +- src/graphbin/utils/graphbin_SGA.py | 2 +- src/graphbin/utils/graphbin_SPAdes.py | 2 +- src/graphbin/utils/labelpropagation/labelprop.py | 2 +- src/graphbin/utils/parsers/__init__.py | 2 +- src/graphbin/utils/parsers/canu_parser.py | 2 +- src/graphbin/utils/parsers/flye_parser.py | 2 +- src/graphbin/utils/parsers/megahit_parser.py | 2 +- src/graphbin/utils/parsers/miniasm_parser.py | 2 +- src/graphbin/utils/parsers/sga_parser.py | 2 +- src/graphbin/utils/parsers/spades_parser.py | 2 +- tests/test_arguments.py | 2 +- tests/test_bidirectional_map.py | 2 +- tests/test_graphbin.py | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index 8480037..a838299 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -22,7 +22,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/utils/graphbin_Canu.py index 005bc4e..43794b3 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/utils/graphbin_Canu.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index ac71a7d..4904b72 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Func.py b/src/graphbin/utils/graphbin_Func.py index f0210dd..56ec8b9 100644 --- a/src/graphbin/utils/graphbin_Func.py +++ b/src/graphbin/utils/graphbin_Func.py @@ -10,7 +10,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/utils/graphbin_MEGAHIT.py index 7eb70cd..9fb1cf1 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/utils/graphbin_MEGAHIT.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/utils/graphbin_Miniasm.py index b09887d..aaac081 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/utils/graphbin_Miniasm.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/utils/graphbin_SGA.py index 6362be0..c866a4d 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/utils/graphbin_SGA.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/utils/graphbin_SPAdes.py index dca186c..1d8a443 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/utils/graphbin_SPAdes.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/utils/labelpropagation/labelprop.py index 0d6caf2..fa520e9 100644 --- a/src/graphbin/utils/labelpropagation/labelprop.py +++ b/src/graphbin/utils/labelpropagation/labelprop.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Lingzhe Teng", "Vijini Mallawaarachchi"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/__init__.py b/src/graphbin/utils/parsers/__init__.py index 5423b36..fe613d5 100644 --- a/src/graphbin/utils/parsers/__init__.py +++ b/src/graphbin/utils/parsers/__init__.py @@ -10,7 +10,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/canu_parser.py b/src/graphbin/utils/parsers/canu_parser.py index c14895e..b9c7821 100644 --- a/src/graphbin/utils/parsers/canu_parser.py +++ b/src/graphbin/utils/parsers/canu_parser.py @@ -16,7 +16,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/flye_parser.py b/src/graphbin/utils/parsers/flye_parser.py index 8076935..e01d723 100644 --- a/src/graphbin/utils/parsers/flye_parser.py +++ b/src/graphbin/utils/parsers/flye_parser.py @@ -18,7 +18,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/megahit_parser.py b/src/graphbin/utils/parsers/megahit_parser.py index 769f325..0e00515 100644 --- a/src/graphbin/utils/parsers/megahit_parser.py +++ b/src/graphbin/utils/parsers/megahit_parser.py @@ -17,7 +17,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/miniasm_parser.py b/src/graphbin/utils/parsers/miniasm_parser.py index b3d5c1a..ff709d2 100644 --- a/src/graphbin/utils/parsers/miniasm_parser.py +++ b/src/graphbin/utils/parsers/miniasm_parser.py @@ -16,7 +16,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/sga_parser.py b/src/graphbin/utils/parsers/sga_parser.py index 78f3147..7ff4988 100644 --- a/src/graphbin/utils/parsers/sga_parser.py +++ b/src/graphbin/utils/parsers/sga_parser.py @@ -17,7 +17,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/spades_parser.py b/src/graphbin/utils/parsers/spades_parser.py index 4a67635..9e2733a 100644 --- a/src/graphbin/utils/parsers/spades_parser.py +++ b/src/graphbin/utils/parsers/spades_parser.py @@ -19,7 +19,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/tests/test_arguments.py b/tests/test_arguments.py index c1c072e..76cec91 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" diff --git a/tests/test_bidirectional_map.py b/tests/test_bidirectional_map.py index 80055e8..ab80209 100644 --- a/tests/test_bidirectional_map.py +++ b/tests/test_bidirectional_map.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" diff --git a/tests/test_graphbin.py b/tests/test_graphbin.py index 57b2919..bde98fd 100644 --- a/tests/test_graphbin.py +++ b/tests/test_graphbin.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.6.1" +__version__ = "1.6.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" From e01c97031d366213600c9389cb8af707b3df1666 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 13:09:13 +0930 Subject: [PATCH 46/76] TST: fix test workflow to solve cogent3 conflict --- .github/workflows/testing_python_conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing_python_conda.yml b/.github/workflows/testing_python_conda.yml index 7765237..78291a5 100644 --- a/.github/workflows/testing_python_conda.yml +++ b/.github/workflows/testing_python_conda.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: os: [macos-12, ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10"] steps: - uses: "actions/checkout@v3" From 354289442b038042999e8d5faa3c9e6343692706 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 14:07:39 +0930 Subject: [PATCH 47/76] DEV: fix #33 --- src/graphbin/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index a838299..04fc541 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -165,6 +165,12 @@ def main( logger.info("Exiting GraphBin... Bye...!") sys.exit(1) + # Check if paths files is provided when the assembler type is Flye + if assembler.lower() == "flye" and paths is None: + logger.error("Please make sure to provide the path to the contigs.paths file.") + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + # Validate prefix if prefix != None: if not prefix.endswith("_"): From 73391208779e17e0a48367c5f64b25671477ad53 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 14:08:54 +0930 Subject: [PATCH 48/76] TST: update tests to check for #33 --- tests/test_arguments.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 76cec91..48ebcec 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -102,6 +102,16 @@ def test_graphbin_spades_path(tmp_dir): exec_wrong_command(cmd) +def test_graphbin_flye_path(tmp_dir): + """test graphbin on flye assembly""" + dir_name = TEST_ROOTDIR / "data" / "1Y3B_Flye" + graph = dir_name / "assembly_graph.gfa" + contigs = dir_name / "assembly.fasta" + binned = dir_name / "initial_binning_res.csv" + cmd = f"graphbin --assembler flye --graph {graph} --contigs {contigs} --binned {binned} --output {tmp_dir}" + exec_wrong_command(cmd) + + def test_graphbin_no_contigs(tmp_dir): """test graphbin with no contigs""" dir_name = TEST_ROOTDIR / "data" / "ESC_metaSPAdes" From 88c516356d1cfa445ca518435def06df85dff7b0 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 17 May 2023 15:40:55 +0930 Subject: [PATCH 49/76] REL: Update version number --- src/graphbin/__init__.py | 2 +- src/graphbin/utils/graphbin_Canu.py | 2 +- src/graphbin/utils/graphbin_Flye.py | 2 +- src/graphbin/utils/graphbin_Func.py | 2 +- src/graphbin/utils/graphbin_MEGAHIT.py | 2 +- src/graphbin/utils/graphbin_Miniasm.py | 2 +- src/graphbin/utils/graphbin_SGA.py | 2 +- src/graphbin/utils/graphbin_SPAdes.py | 2 +- src/graphbin/utils/labelpropagation/labelprop.py | 2 +- src/graphbin/utils/parsers/__init__.py | 2 +- src/graphbin/utils/parsers/canu_parser.py | 2 +- src/graphbin/utils/parsers/flye_parser.py | 2 +- src/graphbin/utils/parsers/megahit_parser.py | 2 +- src/graphbin/utils/parsers/miniasm_parser.py | 2 +- src/graphbin/utils/parsers/sga_parser.py | 2 +- src/graphbin/utils/parsers/spades_parser.py | 2 +- tests/test_arguments.py | 2 +- tests/test_bidirectional_map.py | 2 +- tests/test_graphbin.py | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index 04fc541..a00e0fe 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -22,7 +22,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/utils/graphbin_Canu.py index 43794b3..5937760 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/utils/graphbin_Canu.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index 4904b72..be79b39 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Func.py b/src/graphbin/utils/graphbin_Func.py index 56ec8b9..6f49b6b 100644 --- a/src/graphbin/utils/graphbin_Func.py +++ b/src/graphbin/utils/graphbin_Func.py @@ -10,7 +10,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/utils/graphbin_MEGAHIT.py index 9fb1cf1..3a6260e 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/utils/graphbin_MEGAHIT.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/utils/graphbin_Miniasm.py index aaac081..f7328f3 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/utils/graphbin_Miniasm.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/utils/graphbin_SGA.py index c866a4d..9a06c96 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/utils/graphbin_SGA.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/utils/graphbin_SPAdes.py index 1d8a443..b342b70 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/utils/graphbin_SPAdes.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/utils/labelpropagation/labelprop.py index fa520e9..130e5e6 100644 --- a/src/graphbin/utils/labelpropagation/labelprop.py +++ b/src/graphbin/utils/labelpropagation/labelprop.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Lingzhe Teng", "Vijini Mallawaarachchi"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/__init__.py b/src/graphbin/utils/parsers/__init__.py index fe613d5..d14c806 100644 --- a/src/graphbin/utils/parsers/__init__.py +++ b/src/graphbin/utils/parsers/__init__.py @@ -10,7 +10,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/canu_parser.py b/src/graphbin/utils/parsers/canu_parser.py index b9c7821..c3b45c0 100644 --- a/src/graphbin/utils/parsers/canu_parser.py +++ b/src/graphbin/utils/parsers/canu_parser.py @@ -16,7 +16,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/flye_parser.py b/src/graphbin/utils/parsers/flye_parser.py index e01d723..9f06bb3 100644 --- a/src/graphbin/utils/parsers/flye_parser.py +++ b/src/graphbin/utils/parsers/flye_parser.py @@ -18,7 +18,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/megahit_parser.py b/src/graphbin/utils/parsers/megahit_parser.py index 0e00515..c22076e 100644 --- a/src/graphbin/utils/parsers/megahit_parser.py +++ b/src/graphbin/utils/parsers/megahit_parser.py @@ -17,7 +17,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/miniasm_parser.py b/src/graphbin/utils/parsers/miniasm_parser.py index ff709d2..e124736 100644 --- a/src/graphbin/utils/parsers/miniasm_parser.py +++ b/src/graphbin/utils/parsers/miniasm_parser.py @@ -16,7 +16,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/sga_parser.py b/src/graphbin/utils/parsers/sga_parser.py index 7ff4988..a3b4f33 100644 --- a/src/graphbin/utils/parsers/sga_parser.py +++ b/src/graphbin/utils/parsers/sga_parser.py @@ -17,7 +17,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/spades_parser.py b/src/graphbin/utils/parsers/spades_parser.py index 9e2733a..84eb342 100644 --- a/src/graphbin/utils/parsers/spades_parser.py +++ b/src/graphbin/utils/parsers/spades_parser.py @@ -19,7 +19,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 48ebcec..95849da 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" diff --git a/tests/test_bidirectional_map.py b/tests/test_bidirectional_map.py index ab80209..1a91d91 100644 --- a/tests/test_bidirectional_map.py +++ b/tests/test_bidirectional_map.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" diff --git a/tests/test_graphbin.py b/tests/test_graphbin.py index bde98fd..6a535b5 100644 --- a/tests/test_graphbin.py +++ b/tests/test_graphbin.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.6.2" +__version__ = "1.7.0" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" From 2a1d99e5270ad1a9e67bc728a4111e29156c83eb Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Tue, 25 Jul 2023 09:45:45 +0930 Subject: [PATCH 50/76] DEV: Change python-igraph to igraph for PyPI to fix #43 --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 515954b..52fde30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ keywords = ["genomics", "bioinformatics"] readme = "README.md" license = { file = "LICENSE" } requires-python = ">=3.7" -dependencies = ["python-igraph", "cogent3", "cairocffi", "click"] +dependencies = ["igraph", "cogent3", "cairocffi", "click"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Science/Research", diff --git a/requirements.txt b/requirements.txt index 0de4403..a8e025b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ cogent3 -python-igraph>=0.7.1 +igraph>=0.7.1 cairocffi click \ No newline at end of file From 58533b469bb49ff192699ea641635d6e5cfc1299 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Tue, 25 Jul 2023 09:46:45 +0930 Subject: [PATCH 51/76] DEV: Update environment.yml to use cogent3 from pip --- environment.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index eb08632..ae583db 100644 --- a/environment.yml +++ b/environment.yml @@ -5,7 +5,8 @@ channels: - anaconda dependencies: - python>=3.7.1 - - cogent3 - cairocffi - python-igraph - click + - pip + - cogent3 From fa3fb97084c92dedd86742be536526b64d22ded3 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Tue, 25 Jul 2023 09:53:07 +0930 Subject: [PATCH 52/76] DEV: Update environment.yml to use cogent3 from pip --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index ae583db..b66813f 100644 --- a/environment.yml +++ b/environment.yml @@ -9,4 +9,5 @@ dependencies: - python-igraph - click - pip + - pip: - cogent3 From 830461134678f2efdf135f1708cf806e06323dcf Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Tue, 25 Jul 2023 11:18:06 +0930 Subject: [PATCH 53/76] REL: Update version number --- src/graphbin/__init__.py | 2 +- src/graphbin/utils/graphbin_Canu.py | 2 +- src/graphbin/utils/graphbin_Flye.py | 2 +- src/graphbin/utils/graphbin_Func.py | 2 +- src/graphbin/utils/graphbin_MEGAHIT.py | 2 +- src/graphbin/utils/graphbin_Miniasm.py | 2 +- src/graphbin/utils/graphbin_SGA.py | 2 +- src/graphbin/utils/graphbin_SPAdes.py | 2 +- src/graphbin/utils/labelpropagation/labelprop.py | 2 +- src/graphbin/utils/parsers/__init__.py | 2 +- src/graphbin/utils/parsers/canu_parser.py | 2 +- src/graphbin/utils/parsers/flye_parser.py | 2 +- src/graphbin/utils/parsers/megahit_parser.py | 2 +- src/graphbin/utils/parsers/miniasm_parser.py | 2 +- src/graphbin/utils/parsers/sga_parser.py | 2 +- src/graphbin/utils/parsers/spades_parser.py | 2 +- tests/test_arguments.py | 2 +- tests/test_bidirectional_map.py | 2 +- tests/test_graphbin.py | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index a00e0fe..f2bfbed 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -22,7 +22,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/utils/graphbin_Canu.py index 5937760..73bcdb9 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/utils/graphbin_Canu.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index be79b39..a631cc9 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Func.py b/src/graphbin/utils/graphbin_Func.py index 6f49b6b..9b48d94 100644 --- a/src/graphbin/utils/graphbin_Func.py +++ b/src/graphbin/utils/graphbin_Func.py @@ -10,7 +10,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/utils/graphbin_MEGAHIT.py index 3a6260e..2fce7aa 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/utils/graphbin_MEGAHIT.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/utils/graphbin_Miniasm.py index f7328f3..1302b2a 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/utils/graphbin_Miniasm.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/utils/graphbin_SGA.py index 9a06c96..b43608e 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/utils/graphbin_SGA.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/utils/graphbin_SPAdes.py index b342b70..a374cc9 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/utils/graphbin_SPAdes.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/utils/labelpropagation/labelprop.py index 130e5e6..f96bc0b 100644 --- a/src/graphbin/utils/labelpropagation/labelprop.py +++ b/src/graphbin/utils/labelpropagation/labelprop.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Lingzhe Teng", "Vijini Mallawaarachchi"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/__init__.py b/src/graphbin/utils/parsers/__init__.py index d14c806..011b684 100644 --- a/src/graphbin/utils/parsers/__init__.py +++ b/src/graphbin/utils/parsers/__init__.py @@ -10,7 +10,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/canu_parser.py b/src/graphbin/utils/parsers/canu_parser.py index c3b45c0..9bb4559 100644 --- a/src/graphbin/utils/parsers/canu_parser.py +++ b/src/graphbin/utils/parsers/canu_parser.py @@ -16,7 +16,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/flye_parser.py b/src/graphbin/utils/parsers/flye_parser.py index 9f06bb3..0c30216 100644 --- a/src/graphbin/utils/parsers/flye_parser.py +++ b/src/graphbin/utils/parsers/flye_parser.py @@ -18,7 +18,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/megahit_parser.py b/src/graphbin/utils/parsers/megahit_parser.py index c22076e..83c93c6 100644 --- a/src/graphbin/utils/parsers/megahit_parser.py +++ b/src/graphbin/utils/parsers/megahit_parser.py @@ -17,7 +17,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/miniasm_parser.py b/src/graphbin/utils/parsers/miniasm_parser.py index e124736..a0d4439 100644 --- a/src/graphbin/utils/parsers/miniasm_parser.py +++ b/src/graphbin/utils/parsers/miniasm_parser.py @@ -16,7 +16,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/sga_parser.py b/src/graphbin/utils/parsers/sga_parser.py index a3b4f33..3dea5e3 100644 --- a/src/graphbin/utils/parsers/sga_parser.py +++ b/src/graphbin/utils/parsers/sga_parser.py @@ -17,7 +17,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/spades_parser.py b/src/graphbin/utils/parsers/spades_parser.py index 84eb342..ceb428e 100644 --- a/src/graphbin/utils/parsers/spades_parser.py +++ b/src/graphbin/utils/parsers/spades_parser.py @@ -19,7 +19,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Production" diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 95849da..1d60a4f 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" diff --git a/tests/test_bidirectional_map.py b/tests/test_bidirectional_map.py index 1a91d91..2967574 100644 --- a/tests/test_bidirectional_map.py +++ b/tests/test_bidirectional_map.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" diff --git a/tests/test_graphbin.py b/tests/test_graphbin.py index 6a535b5..ed76342 100644 --- a/tests/test_graphbin.py +++ b/tests/test_graphbin.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.7.0" +__version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" __status__ = "Development" From 2d829a719b5e2f2d55fe0cb2167f38473c81315d Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 2 Aug 2023 10:14:35 +0930 Subject: [PATCH 54/76] REL: Create pypi-publish.yml --- .github/workflows/pypi-publish.yml | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/pypi-publish.yml diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 0000000..bdaab28 --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,39 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} From cf86016ffd454da906b973b21dc96d0feecfc812 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 17 Nov 2023 10:39:18 +1030 Subject: [PATCH 55/76] DOC: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a4b13d..c5b6447 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ graphbin --assembler megahit --graph /path/to/graph_file.gfa --contigs /path/to/ ## Citation If you use GraphBin in your work, please cite GraphBin as, -Vijini Mallawaarachchi, Anuradha Wickramarachchi, Yu Lin. GraphBin: Refined binning of metagenomic contigs using assembly graphs. Bioinformatics, Volume 36, Issue 11, June 2020, Pages 3307–3313, DOI: [10.1093/bioinformatics/btaa180](http://dx.doi.org/10.1093/bioinformatics/btaa180) +Vijini Mallawaarachchi, Anuradha Wickramarachchi, Yu Lin. GraphBin: Refined binning of metagenomic contigs using assembly graphs. Bioinformatics, Volume 36, Issue 11, June 2020, Pages 3307–3313, DOI: [https://doi.org/10.1093/bioinformatics/btaa180](https://doi.org/10.1093/bioinformatics/btaa180) ```bibtex @article{10.1093/bioinformatics/btaa180, From 61e49c4f617ec6c559318cbfe47d90f442b049c5 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 17 Nov 2023 19:00:38 +1030 Subject: [PATCH 56/76] DOC: Update logo --- GraphBin_logo_dark.png | Bin 0 -> 133942 bytes GraphBin_logo_light.png | Bin 0 -> 124248 bytes README.md | 3 ++- 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 GraphBin_logo_dark.png create mode 100644 GraphBin_logo_light.png diff --git a/GraphBin_logo_dark.png b/GraphBin_logo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5f1121d372b8f2fe3b3083508f695bbb31035bea GIT binary patch literal 133942 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 q0`|{SGdC3<( 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 E0|1kGga7~l literal 0 HcmV?d00001 diff --git a/README.md b/README.md index c5b6447..d8007dc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@

- GraphBin logo + GraphBin logo + GraphBin logo

# GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs From 1c8dbdf7b195b7636f789587735ac65b609ebe09 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Fri, 17 Nov 2023 19:14:48 +1030 Subject: [PATCH 57/76] DOC: Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d8007dc..c732f09 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- GraphBin logo - GraphBin logo + GraphBin logo + GraphBin logo

# GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs From c0a664ec140634db5e070f59ec9775aba1ff832c Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 18 Dec 2023 15:20:05 +1030 Subject: [PATCH 58/76] TST: Update testing workflows --- .github/workflows/testing_python_app.yml | 2 +- .github/workflows/testing_python_conda.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testing_python_app.yml b/.github/workflows/testing_python_app.yml index 963b3b7..0a95a81 100644 --- a/.github/workflows/testing_python_app.yml +++ b/.github/workflows/testing_python_app.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: os: [macos-12, ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: "actions/checkout@v3" diff --git a/.github/workflows/testing_python_conda.yml b/.github/workflows/testing_python_conda.yml index 78291a5..b33d8e7 100644 --- a/.github/workflows/testing_python_conda.yml +++ b/.github/workflows/testing_python_conda.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: os: [macos-12, ubuntu-latest] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: "actions/checkout@v3" @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 # Setup conda env - - uses: conda-incubator/setup-miniconda@v2 + - uses: conda-incubator/setup-miniconda@v3 with: activate-environment: graphbin environment-file: environment.yml @@ -43,4 +43,4 @@ jobs: run: | pip install pytest pytest-cov pytest --cov=graphbin --cov-report=xml --cov-append - \ No newline at end of file + From d3bbb27dcdf6db9d6bc4468097cc2e4c2003bba8 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 18 Dec 2023 15:23:12 +1030 Subject: [PATCH 59/76] TST: Update testing workflows --- .github/workflows/testing_python_app.yml | 2 +- .github/workflows/testing_python_conda.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing_python_app.yml b/.github/workflows/testing_python_app.yml index 0a95a81..963b3b7 100644 --- a/.github/workflows/testing_python_app.yml +++ b/.github/workflows/testing_python_app.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: os: [macos-12, ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: "actions/checkout@v3" diff --git a/.github/workflows/testing_python_conda.yml b/.github/workflows/testing_python_conda.yml index b33d8e7..3a7db07 100644 --- a/.github/workflows/testing_python_conda.yml +++ b/.github/workflows/testing_python_conda.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: os: [macos-12, ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: "actions/checkout@v3" From 4f058b50a9f8d00dac421ea032b3870169cbafd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 19:25:37 +0000 Subject: [PATCH 60/76] Bump jinja2 from 3.0.3 to 3.1.3 in /docs Bumps [jinja2](https://github.com/pallets/jinja) from 3.0.3 to 3.1.3. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.0.3...3.1.3) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 3e2bdf5..1f80ad2 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ -jinja2==3.0.3 +jinja2==3.1.3 mkdocs>=1.3.1 babel>=2.9.0 click>=7.0 From ca5fb2fa987fe27ee5b7d3727ddb62df7f0034c2 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Tue, 9 Jul 2024 10:04:25 +0930 Subject: [PATCH 61/76] DOC: Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c732f09..6ef57a4 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ # GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs [![DOI](https://img.shields.io/badge/DOI-10.1093/bioinformatics/btaa180-informational)](https://doi.org/10.1093/bioinformatics/btaa180) -[![Anaconda-Server Badge](https://anaconda.org/bioconda/graphbin/badges/version.svg)](https://anaconda.org/bioconda/graphbin) -[![Anaconda-Server Badge](https://anaconda.org/bioconda/graphbin/badges/downloads.svg)](https://anaconda.org/bioconda/graphbin) +[![install with bioconda](https://img.shields.io/badge/install%20with-bioconda-brightgreen.svg?style=flat)](http://bioconda.github.io/recipes/graphbin/README.html) +[![Conda](https://img.shields.io/conda/v/bioconda/graphbin)](https://anaconda.org/bioconda/graphbin) +[![Conda](https://img.shields.io/conda/dn/bioconda/graphbin)](https://anaconda.org/bioconda/graphbin) [![PyPI version](https://badge.fury.io/py/graphbin.svg)](https://badge.fury.io/py/graphbin) [![Downloads](https://static.pepy.tech/badge/graphbin)](https://pepy.tech/project/graphbin) - [![CI](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml/badge.svg)](https://github.com/metagentools/GraphBin/actions/workflows/testing_python_app.yml) [![codecov](https://codecov.io/gh/metagentools/GraphBin/branch/develop/graph/badge.svg?token=0S310F6QXJ)](https://codecov.io/gh/metagentools/GraphBin) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) From dbbc2a2bf74810334c684366659051f091250343 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 21:52:21 +0000 Subject: [PATCH 62/76] Bump jinja2 from 3.1.3 to 3.1.4 in /docs Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.4. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.3...3.1.4) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 1f80ad2..a8639b3 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ -jinja2==3.1.3 +jinja2==3.1.4 mkdocs>=1.3.1 babel>=2.9.0 click>=7.0 From 8b5331dfb174971156f604bc35a0045749274128 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 24 Jul 2024 12:45:46 +0930 Subject: [PATCH 63/76] DOC: Update docs --- docs/usage.md | 92 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 6f7402c..fce6c6f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -4,42 +4,34 @@ You can see the usage options of GraphBin by typing `graphbin -h` on the command line. For example, ``` -usage: graphbin [-h] [--version] [--graph GRAPH] [--binned BINNED] - [--output OUTPUT] [--prefix PREFIX] - [--max_iteration MAX_ITERATION] - [--diff_threshold DIFF_THRESHOLD] [--assembler ASSEMBLER] - [--paths PATHS] [--contigs CONTIGS] [--delimiter DELIMITER] - -GraphBin Help. GraphBin is a metagenomic contig binning tool that makes use of -the contig connectivity information from the assembly graph to bin contigs. It -utilizes the binning result of an existing binning tool and a label -propagation algorithm to correct mis-binned contigs and predict the labels of -contigs which are discarded due to short length. - -optional arguments: - -h, --help show this help message and exit - --version - --graph GRAPH path to the assembly graph file - --binned BINNED path to the .csv file with the initial binning output - from an existing tool - --output OUTPUT path to the output folder - --prefix PREFIX prefix for the output file - --max_iteration MAX_ITERATION - maximum number of iterations for label propagation - algorithm. [default: 100] - --diff_threshold DIFF_THRESHOLD - difference threshold for label propagation algorithm. - [default: 0.1] - --assembler ASSEMBLER - name of the assembler used (SPAdes, SGA or MEGAHIT). - GraphBin supports Flye, Canu and Miniasm long-read - assemblies as well. - --paths PATHS path to the contigs.paths file, only needed for SPAdes - --contigs CONTIGS path to the contigs.fa file. - --delimiter DELIMITER - delimiter for input/output results. Supports a comma - (,), a semicolon (;), a tab ($'\t'), a space (" ") and - a pipe (|) [default: , (comma)] +Usage: graphbin [OPTIONS] + + GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs + +Options: + --assembler [spades|sga|megahit|flye|canu|miniasm] + name of the assembler used (SPAdes, SGA or + MEGAHIT). GraphBin supports Flye, Canu and + Miniasm long-read assemblies as well. + [required] + --graph PATH path to the assembly graph file [required] + --contigs PATH path to the contigs file [required] + --paths PATH path to the contigs.paths (metaSPAdes) or + assembly.info (metaFlye) file + --binned PATH path to the .csv file with the initial + binning output from an existing tool + [required] + --output PATH path to the output folder [required] + --prefix TEXT prefix for the output file + --max_iteration INTEGER maximum number of iterations for label + propagation algorithm [default: 100] + --diff_threshold FLOAT RANGE difference threshold for label propagation + algorithm [default: 0.1; 0<=x<=1] + --delimiter [,|;|$'\t'|" "] delimiter for input/output results. Supports + a comma (,), a semicolon (;), a tab ($'\t'), + a space (" ") and a pipe (|) [default: ,] + -v, --version Show the version and exit. + --help Show this message and exit. ``` `max_iteration` and `diff_threshold` parameters are set by default to `100` and `0.1` respectively. However, the user can specify them when running GraphBin. @@ -49,8 +41,8 @@ optional arguments: For the SPAdes version, `graphbin` takes in 3 files as inputs (required). * Assembly graph file (in `.gfa` format) -* Contigs file (in FASTA format) -* Paths of contigs (in `.paths` format) +* Contigs file (`contigs.fasta` file in FASTA format) +* Paths of contigs (`contigs.paths` file) * Binning output from an existing tool (in `.csv` format) For the SGA version, `graphbin` takes in 2 files as inputs (required). @@ -65,6 +57,13 @@ For the MEGAHIT version, `graphbin` takes in 3 files as inputs (required). * Contigs file (in FASTA format) * Binning output from an existing tool (in `.csv` format) +For the Flye version, `graphbin` takes in 3 files as inputs (required). + +* Assembly graph file (in `.gfa` format) +* Contigs file (`assembly.fasta` file in FASTA format) +* Paths of contigs (`assembly_info.txt` file) +* Binning output from an existing tool (in `.csv` format) + **Note:** Make sure that the initial binning result consists of contigs belonging to only one bin. GraphBin is designed to handle initial contigs which belong to only one bin. Multiple bins for the initial contigs are not supported. **Note:** You can specify the delimiter for the initial binning result file and the final output file using the delimiter paramter. Enter the following values for different delimiters; `,` for a comma, `;` for a semicolon, `$'\t'` for a tab, `" "` for a space and `|` for a pipe. @@ -98,19 +97,36 @@ k99_18709,bin_1 k99_15596,bin_2 ... ``` +Example Flye binned input +``` +contig_1,bin_1 +contig_2,bin_1 +contig_3,bin_2 +contig_4,bin_1 +contig_5,bin_2 +... +``` + Make sure to follow the steps provided in the [preprocessing section](https://graphbin.readthedocs.io/en/latest/preprocess/) to prepare the initial binning result. ## Example Usage ``` +# metaSPAdes assembly graphbin --assembler spades --graph /path/to/graph_file.gfa --contigs /path/to/contigs.fasta --paths /path/to/paths_file.paths --binned /path/to/binning_result.csv --output /path/to/output_folder ``` ``` +# SGA assembly graphbin --assembler sga --graph /path/to/graph_file.asqg --contigs /path/to/contigs.fa --binned /path/to/binning_result.csv --output /path/to/output_folder ``` ``` +# MEGAHIT assembly graphbin --assembler megahit --graph /path/to/graph_file.gfa --contigs /path/to/contigs.fa --binned /path/to/binning_result.csv --output /path/to/output_folder ``` +``` +# metaFlye assembly +graphbin --assembler flye --graph /path/to/assembly_graph.gfa --contigs /path/to/assembly.fasta --paths /path/to/assembly_info.txt --binned /path/to/binning_result.csv --output /path/to/output_folder +``` ## Output From 7fdb734d5cdfd5572d651f18a1d07b2070ba148e Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Wed, 24 Jul 2024 12:53:52 +0930 Subject: [PATCH 64/76] DOC: Update docs --- docs/usage.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index fce6c6f..27377e3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -127,6 +127,14 @@ graphbin --assembler megahit --graph /path/to/graph_file.gfa --contigs /path/to/ # metaFlye assembly graphbin --assembler flye --graph /path/to/assembly_graph.gfa --contigs /path/to/assembly.fasta --paths /path/to/assembly_info.txt --binned /path/to/binning_result.csv --output /path/to/output_folder ``` +``` +# Canu assembly +graphbin --assembler canu --graph /path/to/assembly.contigs.gfa --contigs /path/to/assembly.contigs.fasta --binned /path/to/binning_result.csv --output /path/to/output_folder +``` +``` +# Miniasm assembly +graphbin --assembler miniasm --graph /path/to/reads.gfa --contigs /path/to/unitigs.fasta --binned /path/to/binning_result.csv --output /path/to/output_folder +``` ## Output From 4061bcdb0bdc3ebe9e0d6cf626616e6a649536be Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Tue, 13 Aug 2024 17:37:03 +0930 Subject: [PATCH 65/76] DOC: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ef57a4..3ef69a5 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ graphbin --assembler megahit --graph /path/to/graph_file.gfa --contigs /path/to/ ## Citation If you use GraphBin in your work, please cite GraphBin as, -Vijini Mallawaarachchi, Anuradha Wickramarachchi, Yu Lin. GraphBin: Refined binning of metagenomic contigs using assembly graphs. Bioinformatics, Volume 36, Issue 11, June 2020, Pages 3307–3313, DOI: [https://doi.org/10.1093/bioinformatics/btaa180](https://doi.org/10.1093/bioinformatics/btaa180) +> Vijini Mallawaarachchi, Anuradha Wickramarachchi, Yu Lin. GraphBin: Refined binning of metagenomic contigs using assembly graphs. Bioinformatics, Volume 36, Issue 11, June 2020, Pages 3307–3313, DOI: [https://doi.org/10.1093/bioinformatics/btaa180](https://doi.org/10.1093/bioinformatics/btaa180) ```bibtex @article{10.1093/bioinformatics/btaa180, From ab93fc59f97e2af626000eae0f42b82a585afcea Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Tue, 13 Aug 2024 20:00:38 +0930 Subject: [PATCH 66/76] TST: Update testing workflows --- .github/workflows/testing_python_app.yml | 2 +- .github/workflows/testing_python_conda.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing_python_app.yml b/.github/workflows/testing_python_app.yml index 963b3b7..1c2f6a1 100644 --- a/.github/workflows/testing_python_app.yml +++ b/.github/workflows/testing_python_app.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - os: [macos-12, ubuntu-latest] + os: [macos-latest, ubuntu-latest] python-version: ["3.8", "3.9", "3.10", "3.11"] steps: diff --git a/.github/workflows/testing_python_conda.yml b/.github/workflows/testing_python_conda.yml index 3a7db07..950df2b 100644 --- a/.github/workflows/testing_python_conda.yml +++ b/.github/workflows/testing_python_conda.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: - os: [macos-12, ubuntu-latest] + os: [macos-latest, ubuntu-latest] python-version: ["3.8", "3.9", "3.10", "3.11"] steps: From 40a28999cd7d6837881c8a7f7a44e0be531a025b Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 19 Aug 2024 09:45:38 +0930 Subject: [PATCH 67/76] DEV: organise code to fix #51 --- environment.yml | 4 +- pyproject.toml | 2 +- src/graphbin/__init__.py | 234 +------------------------------------- src/graphbin/cli.py | 240 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 235 deletions(-) create mode 100644 src/graphbin/cli.py diff --git a/environment.yml b/environment.yml index b66813f..cb8ee41 100644 --- a/environment.yml +++ b/environment.yml @@ -5,9 +5,9 @@ channels: - anaconda dependencies: - python>=3.7.1 + - flit - cairocffi - python-igraph - click - pip - - pip: - - cogent3 + - cogent3 diff --git a/pyproject.toml b/pyproject.toml index 52fde30..b3a8371 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ Documentation = "https://graphbin.readthedocs.io/en/latest/" "Source Code" = "https://github.com/metagentools/GraphBin/" [project.scripts] -graphbin = "graphbin:main" +graphbin = "graphbin.cli:main" [project.optional-dependencies] test = [ diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index f2bfbed..6e4a207 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -1,22 +1,4 @@ -#!/usr/bin/env python3 - -"""graphbin: Refined binning of metagenomic contigs using assembly graphs.""" - -import logging -import os -import sys - -import click - -from graphbin.utils import ( - graphbin_Canu, - graphbin_Flye, - graphbin_MEGAHIT, - graphbin_Miniasm, - graphbin_SGA, - graphbin_SPAdes, -) - +"""GraphBin: Refined binning of metagenomic contigs using assembly graphs.""" __author__ = "Vijini Mallawaarachchi" __copyright__ = "Copyright 2019-2022, GraphBin Project" @@ -25,216 +7,4 @@ __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "vijini.mallawaarachchi@anu.edu.au" -__status__ = "Production" - - -class ArgsObj: - def __init__( - self, - assembler, - graph, - contigs, - paths, - binned, - output, - prefix, - max_iteration, - diff_threshold, - delimiter, - ): - self.assembler = assembler - self.graph = graph - self.contigs = contigs - self.paths = paths - self.binned = binned - self.output = output - self.prefix = prefix - self.max_iteration = max_iteration - self.diff_threshold = diff_threshold - self.delimiter = delimiter - - -@click.command() -@click.option( - "--assembler", - help="name of the assembler used (SPAdes, SGA or MEGAHIT). GraphBin supports Flye, Canu and Miniasm long-read assemblies as well.", - type=click.Choice( - ["spades", "sga", "megahit", "flye", "canu", "miniasm"], case_sensitive=False - ), - required=True, -) -@click.option( - "--graph", - help="path to the assembly graph file", - type=click.Path(exists=True), - required=True, -) -@click.option( - "--contigs", - help="path to the contigs file", - type=click.Path(exists=True), - required=True, -) -@click.option( - "--paths", - help="path to the contigs.paths (metaSPAdes) or assembly.info (metaFlye) file", - type=click.Path(exists=True), - required=False, -) -@click.option( - "--binned", - help="path to the .csv file with the initial binning output from an existing tool", - type=click.Path(exists=True), - required=True, -) -@click.option( - "--output", - help="path to the output folder", - type=click.Path(dir_okay=True, writable=True, readable=True), - required=True, -) -@click.option( - "--prefix", - help="prefix for the output file", - type=str, - required=False, -) -@click.option( - "--max_iteration", - help="maximum number of iterations for label propagation algorithm", - type=int, - default=100, - show_default=True, - required=False, -) -@click.option( - "--diff_threshold", - help="difference threshold for label propagation algorithm", - type=click.FloatRange(0, 1), - default=0.1, - show_default=True, - required=False, -) -@click.option( - "--delimiter", - help="delimiter for input/output results. Supports a comma (,), a semicolon (;), a tab ($'\\t'), a space (\" \") and a pipe (|)", - type=click.Choice([",", ";", "$'\\t'", '" "'], case_sensitive=False), - default=",", - show_default=True, - required=False, -) -@click.version_option(__version__, "-v", "--version", is_flag=True) -def main( - assembler, - graph, - contigs, - paths, - binned, - output, - prefix, - max_iteration, - diff_threshold, - delimiter, -): - """ - GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs - """ - - # Setup logger - # --------------------------------------------------- - - logger = logging.getLogger("GraphBin %s" % __version__) - logger.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") - consoleHeader = logging.StreamHandler() - consoleHeader.setFormatter(formatter) - consoleHeader.setLevel(logging.INFO) - logger.addHandler(consoleHeader) - - fileHandler = logging.FileHandler(f"{output}{prefix}graphbin.log") - fileHandler.setLevel(logging.DEBUG) - fileHandler.setFormatter(formatter) - logger.addHandler(fileHandler) - - # Validate options - # --------------------------------------------------- - - # Check if paths files is provided when the assembler type is SPAdes - if assembler.lower() == "spades" and paths is None: - logger.error("Please make sure to provide the path to the contigs.paths file.") - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Check if paths files is provided when the assembler type is Flye - if assembler.lower() == "flye" and paths is None: - logger.error("Please make sure to provide the path to the contigs.paths file.") - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Validate prefix - if prefix != None: - if not prefix.endswith("_"): - prefix = prefix + "_" - else: - prefix = "" - - # Validate max_iteration - if max_iteration <= 0: - logger.error("Please enter a valid number for max_iteration") - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # Validate diff_threshold - if diff_threshold < 0: - logger.error("Please enter a valid number for diff_threshold") - logger.info("Exiting GraphBin... Bye...!") - sys.exit(1) - - # # Remove previous files if they exist - # if os.path.exists(f"{output}{prefix}graphbin.log"): - # os.remove(f"{output}{prefix}graphbin.log") - # if os.path.exists(f"{output}{prefix}graphbin_output.csv"): - # os.remove(f"{output}{prefix}graphbin_output.csv") - # if os.path.exists(f"{output}{prefix}graphbin_unbinned.csv"): - # os.remove(f"{output}{prefix}graphbin_unbinned.csv") - - # Make args object - args = ArgsObj( - assembler, - graph, - contigs, - paths, - binned, - output, - prefix, - max_iteration, - diff_threshold, - delimiter, - ) - - # Run GraphBin - # --------------------------------------------------- - if assembler.lower() == "canu": - graphbin_Canu.main(args) - if assembler.lower() == "flye": - graphbin_Flye.main(args) - if assembler.lower() == "megahit": - graphbin_MEGAHIT.main(args) - if assembler.lower() == "miniasm": - graphbin_Miniasm.main(args) - if assembler.lower() == "sga": - graphbin_SGA.main(args) - if assembler.lower() == "spades": - graphbin_SPAdes.main(args) - - # Exit program - # -------------- - - logger.info("Thank you for using GraphBin! Bye...!") - - logger.removeHandler(fileHandler) - logger.removeHandler(consoleHeader) - - -if __name__ == "__main__": - main() +__status__ = "Production" \ No newline at end of file diff --git a/src/graphbin/cli.py b/src/graphbin/cli.py new file mode 100644 index 0000000..f2bfbed --- /dev/null +++ b/src/graphbin/cli.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 + +"""graphbin: Refined binning of metagenomic contigs using assembly graphs.""" + +import logging +import os +import sys + +import click + +from graphbin.utils import ( + graphbin_Canu, + graphbin_Flye, + graphbin_MEGAHIT, + graphbin_Miniasm, + graphbin_SGA, + graphbin_SPAdes, +) + + +__author__ = "Vijini Mallawaarachchi" +__copyright__ = "Copyright 2019-2022, GraphBin Project" +__credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] +__license__ = "BSD-3" +__version__ = "1.7.1" +__maintainer__ = "Vijini Mallawaarachchi" +__email__ = "vijini.mallawaarachchi@anu.edu.au" +__status__ = "Production" + + +class ArgsObj: + def __init__( + self, + assembler, + graph, + contigs, + paths, + binned, + output, + prefix, + max_iteration, + diff_threshold, + delimiter, + ): + self.assembler = assembler + self.graph = graph + self.contigs = contigs + self.paths = paths + self.binned = binned + self.output = output + self.prefix = prefix + self.max_iteration = max_iteration + self.diff_threshold = diff_threshold + self.delimiter = delimiter + + +@click.command() +@click.option( + "--assembler", + help="name of the assembler used (SPAdes, SGA or MEGAHIT). GraphBin supports Flye, Canu and Miniasm long-read assemblies as well.", + type=click.Choice( + ["spades", "sga", "megahit", "flye", "canu", "miniasm"], case_sensitive=False + ), + required=True, +) +@click.option( + "--graph", + help="path to the assembly graph file", + type=click.Path(exists=True), + required=True, +) +@click.option( + "--contigs", + help="path to the contigs file", + type=click.Path(exists=True), + required=True, +) +@click.option( + "--paths", + help="path to the contigs.paths (metaSPAdes) or assembly.info (metaFlye) file", + type=click.Path(exists=True), + required=False, +) +@click.option( + "--binned", + help="path to the .csv file with the initial binning output from an existing tool", + type=click.Path(exists=True), + required=True, +) +@click.option( + "--output", + help="path to the output folder", + type=click.Path(dir_okay=True, writable=True, readable=True), + required=True, +) +@click.option( + "--prefix", + help="prefix for the output file", + type=str, + required=False, +) +@click.option( + "--max_iteration", + help="maximum number of iterations for label propagation algorithm", + type=int, + default=100, + show_default=True, + required=False, +) +@click.option( + "--diff_threshold", + help="difference threshold for label propagation algorithm", + type=click.FloatRange(0, 1), + default=0.1, + show_default=True, + required=False, +) +@click.option( + "--delimiter", + help="delimiter for input/output results. Supports a comma (,), a semicolon (;), a tab ($'\\t'), a space (\" \") and a pipe (|)", + type=click.Choice([",", ";", "$'\\t'", '" "'], case_sensitive=False), + default=",", + show_default=True, + required=False, +) +@click.version_option(__version__, "-v", "--version", is_flag=True) +def main( + assembler, + graph, + contigs, + paths, + binned, + output, + prefix, + max_iteration, + diff_threshold, + delimiter, +): + """ + GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs + """ + + # Setup logger + # --------------------------------------------------- + + logger = logging.getLogger("GraphBin %s" % __version__) + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") + consoleHeader = logging.StreamHandler() + consoleHeader.setFormatter(formatter) + consoleHeader.setLevel(logging.INFO) + logger.addHandler(consoleHeader) + + fileHandler = logging.FileHandler(f"{output}{prefix}graphbin.log") + fileHandler.setLevel(logging.DEBUG) + fileHandler.setFormatter(formatter) + logger.addHandler(fileHandler) + + # Validate options + # --------------------------------------------------- + + # Check if paths files is provided when the assembler type is SPAdes + if assembler.lower() == "spades" and paths is None: + logger.error("Please make sure to provide the path to the contigs.paths file.") + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + # Check if paths files is provided when the assembler type is Flye + if assembler.lower() == "flye" and paths is None: + logger.error("Please make sure to provide the path to the contigs.paths file.") + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + # Validate prefix + if prefix != None: + if not prefix.endswith("_"): + prefix = prefix + "_" + else: + prefix = "" + + # Validate max_iteration + if max_iteration <= 0: + logger.error("Please enter a valid number for max_iteration") + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + # Validate diff_threshold + if diff_threshold < 0: + logger.error("Please enter a valid number for diff_threshold") + logger.info("Exiting GraphBin... Bye...!") + sys.exit(1) + + # # Remove previous files if they exist + # if os.path.exists(f"{output}{prefix}graphbin.log"): + # os.remove(f"{output}{prefix}graphbin.log") + # if os.path.exists(f"{output}{prefix}graphbin_output.csv"): + # os.remove(f"{output}{prefix}graphbin_output.csv") + # if os.path.exists(f"{output}{prefix}graphbin_unbinned.csv"): + # os.remove(f"{output}{prefix}graphbin_unbinned.csv") + + # Make args object + args = ArgsObj( + assembler, + graph, + contigs, + paths, + binned, + output, + prefix, + max_iteration, + diff_threshold, + delimiter, + ) + + # Run GraphBin + # --------------------------------------------------- + if assembler.lower() == "canu": + graphbin_Canu.main(args) + if assembler.lower() == "flye": + graphbin_Flye.main(args) + if assembler.lower() == "megahit": + graphbin_MEGAHIT.main(args) + if assembler.lower() == "miniasm": + graphbin_Miniasm.main(args) + if assembler.lower() == "sga": + graphbin_SGA.main(args) + if assembler.lower() == "spades": + graphbin_SPAdes.main(args) + + # Exit program + # -------------- + + logger.info("Thank you for using GraphBin! Bye...!") + + logger.removeHandler(fileHandler) + logger.removeHandler(consoleHeader) + + +if __name__ == "__main__": + main() From 97efa15e0887fbea65201e89993bfb5840c07671 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 19 Aug 2024 09:58:21 +0930 Subject: [PATCH 68/76] MAINT: update emails --- pyproject.toml | 4 ++-- src/graphbin/__init__.py | 2 +- src/graphbin/cli.py | 2 +- src/graphbin/support/gfa2fasta.py | 2 +- src/graphbin/support/prep_result.py | 2 +- src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py | 2 +- src/graphbin/support/visualise_result_MEGAHIT.py | 2 +- src/graphbin/support/visualise_result_SGA.py | 2 +- src/graphbin/support/visualise_result_SPAdes.py | 2 +- src/graphbin/utils/graphbin_Canu.py | 2 +- src/graphbin/utils/graphbin_Flye.py | 2 +- src/graphbin/utils/graphbin_Func.py | 2 +- src/graphbin/utils/graphbin_MEGAHIT.py | 2 +- src/graphbin/utils/graphbin_Miniasm.py | 2 +- src/graphbin/utils/graphbin_SGA.py | 2 +- src/graphbin/utils/graphbin_SPAdes.py | 2 +- src/graphbin/utils/labelpropagation/labelprop.py | 2 +- src/graphbin/utils/parsers/__init__.py | 2 +- src/graphbin/utils/parsers/canu_parser.py | 2 +- src/graphbin/utils/parsers/flye_parser.py | 2 +- src/graphbin/utils/parsers/megahit_parser.py | 2 +- src/graphbin/utils/parsers/miniasm_parser.py | 2 +- src/graphbin/utils/parsers/sga_parser.py | 2 +- src/graphbin/utils/parsers/spades_parser.py | 2 +- tests/test_arguments.py | 2 +- tests/test_bidirectional_map.py | 2 +- tests/test_graphbin.py | 2 +- 27 files changed, 28 insertions(+), 28 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b3a8371..bb85f7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,8 +5,8 @@ build-backend = "flit_core.buildapi" [project] name = "graphbin" authors = [ - { name = "Vijini Mallawaarachchi", email = "Vijini.Mallawaarachchi@anu.edu.au"}, - { name = "Anuradha Wickramarachchi", email = "Anuradha.Wickramarachchi@anu.edu.au"}, + { name = "Vijini Mallawaarachchi", email = "viji.mallawaarachchi@gmail.com"}, + { name = "Anuradha Wickramarachchi", email = "anuradhawick@gmail.com"}, { name = "Yu Lin", email = "yu.lin@anu.edu.au"}, ] keywords = ["genomics", "bioinformatics"] diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index 6e4a207..5e6c08a 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -6,5 +6,5 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" \ No newline at end of file diff --git a/src/graphbin/cli.py b/src/graphbin/cli.py index f2bfbed..5cd4f95 100644 --- a/src/graphbin/cli.py +++ b/src/graphbin/cli.py @@ -24,7 +24,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/support/gfa2fasta.py b/src/graphbin/support/gfa2fasta.py index b4c39c0..548c51e 100644 --- a/src/graphbin/support/gfa2fasta.py +++ b/src/graphbin/support/gfa2fasta.py @@ -20,7 +20,7 @@ __license__ = "BSD-3" __type__ = "Support Script" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/support/prep_result.py b/src/graphbin/support/prep_result.py index f37f4ee..a756cc9 100644 --- a/src/graphbin/support/prep_result.py +++ b/src/graphbin/support/prep_result.py @@ -24,7 +24,7 @@ __license__ = "BSD-3" __type__ = "Support Script" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py b/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py index df618ac..eec576f 100644 --- a/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py +++ b/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py @@ -26,7 +26,7 @@ __license__ = "BSD-3" __type__ = "Support Script" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/support/visualise_result_MEGAHIT.py b/src/graphbin/support/visualise_result_MEGAHIT.py index a0b6c42..33e35fa 100644 --- a/src/graphbin/support/visualise_result_MEGAHIT.py +++ b/src/graphbin/support/visualise_result_MEGAHIT.py @@ -28,7 +28,7 @@ __license__ = "BSD-3" __type__ = "Support Script" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/support/visualise_result_SGA.py b/src/graphbin/support/visualise_result_SGA.py index 88ee0d1..89117a3 100644 --- a/src/graphbin/support/visualise_result_SGA.py +++ b/src/graphbin/support/visualise_result_SGA.py @@ -27,7 +27,7 @@ __license__ = "BSD-3" __type__ = "Support Script" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/support/visualise_result_SPAdes.py b/src/graphbin/support/visualise_result_SPAdes.py index 88e8ff4..dfdcd9b 100644 --- a/src/graphbin/support/visualise_result_SPAdes.py +++ b/src/graphbin/support/visualise_result_SPAdes.py @@ -27,7 +27,7 @@ __license__ = "BSD-3" __type__ = "Support Script" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/utils/graphbin_Canu.py index 73bcdb9..4408254 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/utils/graphbin_Canu.py @@ -29,7 +29,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" # create logger diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index a631cc9..78f8f9b 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -29,7 +29,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" # create logger diff --git a/src/graphbin/utils/graphbin_Func.py b/src/graphbin/utils/graphbin_Func.py index 9b48d94..a63bc2f 100644 --- a/src/graphbin/utils/graphbin_Func.py +++ b/src/graphbin/utils/graphbin_Func.py @@ -12,7 +12,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" logger = logging.getLogger("GraphBin %s" % __version__) diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/utils/graphbin_MEGAHIT.py index 2fce7aa..0eb7a9e 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/utils/graphbin_MEGAHIT.py @@ -30,7 +30,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" # create logger diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/utils/graphbin_Miniasm.py index 1302b2a..8ae0589 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/utils/graphbin_Miniasm.py @@ -29,7 +29,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" # create logger diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/utils/graphbin_SGA.py index b43608e..0b34866 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/utils/graphbin_SGA.py @@ -30,7 +30,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" # create logger diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/utils/graphbin_SPAdes.py index a374cc9..2a17531 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/utils/graphbin_SPAdes.py @@ -29,7 +29,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" # create logger diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/utils/labelpropagation/labelprop.py index f96bc0b..d8b90e3 100644 --- a/src/graphbin/utils/labelpropagation/labelprop.py +++ b/src/graphbin/utils/labelpropagation/labelprop.py @@ -13,7 +13,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" # create logger diff --git a/src/graphbin/utils/parsers/__init__.py b/src/graphbin/utils/parsers/__init__.py index 011b684..db17ad3 100644 --- a/src/graphbin/utils/parsers/__init__.py +++ b/src/graphbin/utils/parsers/__init__.py @@ -12,7 +12,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/canu_parser.py b/src/graphbin/utils/parsers/canu_parser.py index 9bb4559..0e4f235 100644 --- a/src/graphbin/utils/parsers/canu_parser.py +++ b/src/graphbin/utils/parsers/canu_parser.py @@ -18,7 +18,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/flye_parser.py b/src/graphbin/utils/parsers/flye_parser.py index 0c30216..5cbe0c0 100644 --- a/src/graphbin/utils/parsers/flye_parser.py +++ b/src/graphbin/utils/parsers/flye_parser.py @@ -20,7 +20,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/megahit_parser.py b/src/graphbin/utils/parsers/megahit_parser.py index 83c93c6..42928d9 100644 --- a/src/graphbin/utils/parsers/megahit_parser.py +++ b/src/graphbin/utils/parsers/megahit_parser.py @@ -19,7 +19,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/miniasm_parser.py b/src/graphbin/utils/parsers/miniasm_parser.py index a0d4439..6be95ed 100644 --- a/src/graphbin/utils/parsers/miniasm_parser.py +++ b/src/graphbin/utils/parsers/miniasm_parser.py @@ -18,7 +18,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/sga_parser.py b/src/graphbin/utils/parsers/sga_parser.py index 3dea5e3..20cc1d7 100644 --- a/src/graphbin/utils/parsers/sga_parser.py +++ b/src/graphbin/utils/parsers/sga_parser.py @@ -19,7 +19,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/spades_parser.py b/src/graphbin/utils/parsers/spades_parser.py index ceb428e..00b9dec 100644 --- a/src/graphbin/utils/parsers/spades_parser.py +++ b/src/graphbin/utils/parsers/spades_parser.py @@ -21,7 +21,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 1d60a4f..8d76c7f 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -11,7 +11,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Development" diff --git a/tests/test_bidirectional_map.py b/tests/test_bidirectional_map.py index 2967574..50901ae 100644 --- a/tests/test_bidirectional_map.py +++ b/tests/test_bidirectional_map.py @@ -13,7 +13,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Development" diff --git a/tests/test_graphbin.py b/tests/test_graphbin.py index ed76342..fbd0eec 100644 --- a/tests/test_graphbin.py +++ b/tests/test_graphbin.py @@ -11,7 +11,7 @@ __license__ = "BSD-3" __version__ = "1.7.1" __maintainer__ = "Vijini Mallawaarachchi" -__email__ = "vijini.mallawaarachchi@anu.edu.au" +__email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Development" From 7ba2cea6742e4118b47730e2dafd5cbd448b9ed3 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 19 Aug 2024 09:59:09 +0930 Subject: [PATCH 69/76] REL: update version number --- src/graphbin/__init__.py | 2 +- src/graphbin/cli.py | 2 +- src/graphbin/utils/graphbin_Canu.py | 2 +- src/graphbin/utils/graphbin_Flye.py | 2 +- src/graphbin/utils/graphbin_Func.py | 2 +- src/graphbin/utils/graphbin_MEGAHIT.py | 2 +- src/graphbin/utils/graphbin_Miniasm.py | 2 +- src/graphbin/utils/graphbin_SGA.py | 2 +- src/graphbin/utils/graphbin_SPAdes.py | 2 +- src/graphbin/utils/labelpropagation/labelprop.py | 2 +- src/graphbin/utils/parsers/__init__.py | 2 +- src/graphbin/utils/parsers/canu_parser.py | 2 +- src/graphbin/utils/parsers/flye_parser.py | 2 +- src/graphbin/utils/parsers/megahit_parser.py | 2 +- src/graphbin/utils/parsers/miniasm_parser.py | 2 +- src/graphbin/utils/parsers/sga_parser.py | 2 +- src/graphbin/utils/parsers/spades_parser.py | 2 +- tests/test_arguments.py | 2 +- tests/test_bidirectional_map.py | 2 +- tests/test_graphbin.py | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index 5e6c08a..1c9e8d6 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -4,7 +4,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" \ No newline at end of file diff --git a/src/graphbin/cli.py b/src/graphbin/cli.py index 5cd4f95..6f177ec 100644 --- a/src/graphbin/cli.py +++ b/src/graphbin/cli.py @@ -22,7 +22,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/utils/graphbin_Canu.py index 4408254..fd0e650 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/utils/graphbin_Canu.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index 78f8f9b..1f8be39 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Func.py b/src/graphbin/utils/graphbin_Func.py index a63bc2f..f6975a7 100644 --- a/src/graphbin/utils/graphbin_Func.py +++ b/src/graphbin/utils/graphbin_Func.py @@ -10,7 +10,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/utils/graphbin_MEGAHIT.py index 0eb7a9e..0c44489 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/utils/graphbin_MEGAHIT.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/utils/graphbin_Miniasm.py index 8ae0589..b5235fc 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/utils/graphbin_Miniasm.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/utils/graphbin_SGA.py index 0b34866..30e597d 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/utils/graphbin_SGA.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/utils/graphbin_SPAdes.py index 2a17531..2860b95 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/utils/graphbin_SPAdes.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/utils/labelpropagation/labelprop.py index d8b90e3..b545127 100644 --- a/src/graphbin/utils/labelpropagation/labelprop.py +++ b/src/graphbin/utils/labelpropagation/labelprop.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Lingzhe Teng", "Vijini Mallawaarachchi"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/__init__.py b/src/graphbin/utils/parsers/__init__.py index db17ad3..f001668 100644 --- a/src/graphbin/utils/parsers/__init__.py +++ b/src/graphbin/utils/parsers/__init__.py @@ -10,7 +10,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/canu_parser.py b/src/graphbin/utils/parsers/canu_parser.py index 0e4f235..efc42c5 100644 --- a/src/graphbin/utils/parsers/canu_parser.py +++ b/src/graphbin/utils/parsers/canu_parser.py @@ -16,7 +16,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/flye_parser.py b/src/graphbin/utils/parsers/flye_parser.py index 5cbe0c0..ba9a138 100644 --- a/src/graphbin/utils/parsers/flye_parser.py +++ b/src/graphbin/utils/parsers/flye_parser.py @@ -18,7 +18,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/megahit_parser.py b/src/graphbin/utils/parsers/megahit_parser.py index 42928d9..499cf7e 100644 --- a/src/graphbin/utils/parsers/megahit_parser.py +++ b/src/graphbin/utils/parsers/megahit_parser.py @@ -17,7 +17,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/miniasm_parser.py b/src/graphbin/utils/parsers/miniasm_parser.py index 6be95ed..b6e035b 100644 --- a/src/graphbin/utils/parsers/miniasm_parser.py +++ b/src/graphbin/utils/parsers/miniasm_parser.py @@ -16,7 +16,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/sga_parser.py b/src/graphbin/utils/parsers/sga_parser.py index 20cc1d7..ed9215f 100644 --- a/src/graphbin/utils/parsers/sga_parser.py +++ b/src/graphbin/utils/parsers/sga_parser.py @@ -17,7 +17,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/utils/parsers/spades_parser.py b/src/graphbin/utils/parsers/spades_parser.py index 00b9dec..df7cebd 100644 --- a/src/graphbin/utils/parsers/spades_parser.py +++ b/src/graphbin/utils/parsers/spades_parser.py @@ -19,7 +19,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 8d76c7f..92147e6 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Development" diff --git a/tests/test_bidirectional_map.py b/tests/test_bidirectional_map.py index 50901ae..ac52a4c 100644 --- a/tests/test_bidirectional_map.py +++ b/tests/test_bidirectional_map.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Development" diff --git a/tests/test_graphbin.py b/tests/test_graphbin.py index fbd0eec..7adb335 100644 --- a/tests/test_graphbin.py +++ b/tests/test_graphbin.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.7.1" +__version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Development" From 32e89c4ff8b84ec724dc5f821478350b1c2b3fb7 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 19 Aug 2024 10:02:02 +0930 Subject: [PATCH 70/76] MAINT: logger formatting --- src/graphbin/cli.py | 2 +- src/graphbin/utils/graphbin_Canu.py | 2 +- src/graphbin/utils/graphbin_Flye.py | 2 +- src/graphbin/utils/graphbin_Func.py | 2 +- src/graphbin/utils/graphbin_MEGAHIT.py | 2 +- src/graphbin/utils/graphbin_Miniasm.py | 2 +- src/graphbin/utils/graphbin_SGA.py | 2 +- src/graphbin/utils/graphbin_SPAdes.py | 2 +- src/graphbin/utils/labelpropagation/labelprop.py | 4 ++-- src/graphbin/utils/parsers/__init__.py | 2 +- src/graphbin/utils/parsers/canu_parser.py | 2 +- src/graphbin/utils/parsers/flye_parser.py | 2 +- src/graphbin/utils/parsers/megahit_parser.py | 2 +- src/graphbin/utils/parsers/miniasm_parser.py | 2 +- src/graphbin/utils/parsers/sga_parser.py | 2 +- src/graphbin/utils/parsers/spades_parser.py | 2 +- 16 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/graphbin/cli.py b/src/graphbin/cli.py index 6f177ec..6647afa 100644 --- a/src/graphbin/cli.py +++ b/src/graphbin/cli.py @@ -143,7 +143,7 @@ def main( # Setup logger # --------------------------------------------------- - logger = logging.getLogger("GraphBin %s" % __version__) + logger = logging.getLogger(f"GraphBin {__version__}") logger.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") consoleHeader = logging.StreamHandler() diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/utils/graphbin_Canu.py index fd0e650..1ef8f77 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/utils/graphbin_Canu.py @@ -33,7 +33,7 @@ __status__ = "Production" # create logger -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def run(args): diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/utils/graphbin_Flye.py index 1f8be39..b6d44a3 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/utils/graphbin_Flye.py @@ -33,7 +33,7 @@ __status__ = "Production" # create logger -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def run(args): diff --git a/src/graphbin/utils/graphbin_Func.py b/src/graphbin/utils/graphbin_Func.py index f6975a7..fc03a7f 100644 --- a/src/graphbin/utils/graphbin_Func.py +++ b/src/graphbin/utils/graphbin_Func.py @@ -15,7 +15,7 @@ __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") MIN_BIN_COUNT = 10 diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/utils/graphbin_MEGAHIT.py index 0c44489..238fa43 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/utils/graphbin_MEGAHIT.py @@ -34,7 +34,7 @@ __status__ = "Production" # create logger -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def run(args): diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/utils/graphbin_Miniasm.py index b5235fc..90ad17a 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/utils/graphbin_Miniasm.py @@ -33,7 +33,7 @@ __status__ = "Production" # create logger -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def run(args): diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/utils/graphbin_SGA.py index 30e597d..b377313 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/utils/graphbin_SGA.py @@ -34,7 +34,7 @@ __status__ = "Production" # create logger -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def run(args): diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/utils/graphbin_SPAdes.py index 2860b95..d93e9a7 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/utils/graphbin_SPAdes.py @@ -33,7 +33,7 @@ __status__ = "Production" # create logger -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def run(args): diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/utils/labelpropagation/labelprop.py index b545127..8277fd4 100644 --- a/src/graphbin/utils/labelpropagation/labelprop.py +++ b/src/graphbin/utils/labelpropagation/labelprop.py @@ -17,7 +17,7 @@ __status__ = "Production" # create logger -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") class Edge: @@ -29,7 +29,7 @@ def __init__(self, src, dest, weight): class LabelProp: def __init__(self): - self.logger = logging.getLogger("GraphBin %s" % __version__) + self.logger = logging.getLogger(f"GraphBin {__version__}") self.logger.info("Creating an instance of LabelProp") self.initialize_env() diff --git a/src/graphbin/utils/parsers/__init__.py b/src/graphbin/utils/parsers/__init__.py index f001668..73645a5 100644 --- a/src/graphbin/utils/parsers/__init__.py +++ b/src/graphbin/utils/parsers/__init__.py @@ -16,7 +16,7 @@ __status__ = "Production" -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def get_initial_bin_count(contig_bins_file, delimiter): diff --git a/src/graphbin/utils/parsers/canu_parser.py b/src/graphbin/utils/parsers/canu_parser.py index efc42c5..233b943 100644 --- a/src/graphbin/utils/parsers/canu_parser.py +++ b/src/graphbin/utils/parsers/canu_parser.py @@ -22,7 +22,7 @@ __status__ = "Production" -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def get_initial_binning_result( diff --git a/src/graphbin/utils/parsers/flye_parser.py b/src/graphbin/utils/parsers/flye_parser.py index ba9a138..c980f86 100644 --- a/src/graphbin/utils/parsers/flye_parser.py +++ b/src/graphbin/utils/parsers/flye_parser.py @@ -24,7 +24,7 @@ __status__ = "Production" -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def get_initial_binning_result( diff --git a/src/graphbin/utils/parsers/megahit_parser.py b/src/graphbin/utils/parsers/megahit_parser.py index 499cf7e..e748e3d 100644 --- a/src/graphbin/utils/parsers/megahit_parser.py +++ b/src/graphbin/utils/parsers/megahit_parser.py @@ -23,7 +23,7 @@ __status__ = "Production" -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def get_initial_binning_result( diff --git a/src/graphbin/utils/parsers/miniasm_parser.py b/src/graphbin/utils/parsers/miniasm_parser.py index b6e035b..5aec23c 100644 --- a/src/graphbin/utils/parsers/miniasm_parser.py +++ b/src/graphbin/utils/parsers/miniasm_parser.py @@ -22,7 +22,7 @@ __status__ = "Production" -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def get_initial_binning_result( diff --git a/src/graphbin/utils/parsers/sga_parser.py b/src/graphbin/utils/parsers/sga_parser.py index ed9215f..d56b950 100644 --- a/src/graphbin/utils/parsers/sga_parser.py +++ b/src/graphbin/utils/parsers/sga_parser.py @@ -23,7 +23,7 @@ __status__ = "Production" -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def get_initial_binning_result( diff --git a/src/graphbin/utils/parsers/spades_parser.py b/src/graphbin/utils/parsers/spades_parser.py index df7cebd..0c69ff7 100644 --- a/src/graphbin/utils/parsers/spades_parser.py +++ b/src/graphbin/utils/parsers/spades_parser.py @@ -25,7 +25,7 @@ __status__ = "Production" -logger = logging.getLogger("GraphBin %s" % __version__) +logger = logging.getLogger(f"GraphBin {__version__}") def get_initial_binning_result( From 3f17e918bbaaf29f6f3b077c8f0b5c2084f7abaf Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 19 Aug 2024 10:04:28 +0930 Subject: [PATCH 71/76] MAINT: clean-up files --- MANIFEST.in | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index c9b7e5a..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,13 +0,0 @@ -include *.md -include LICENSE - -recursive-include graphbin_utils/ * -recursive-include graphbin_utils/bidirectionalmap * -recursive-include graphbin_utils/labelpropagation * - -global-exclude graphbin_utils/ *.pyc -global-exclude graphbin_utils/__pycache__ *.pyc -global-exclude graphbin_utils/bidirectionalmap *.pyc -global-exclude graphbin_utils/bidirectionalmap/__pycache__ *.pyc -global-exclude graphbin_utils/labelpropagation *.pyc -global-exclude graphbin_utils/labelpropagation/__pycache__ *.pyc From 851a9b29d975ce5a92a47fb5a660bd4e49fd60d5 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 19 Aug 2024 10:05:07 +0930 Subject: [PATCH 72/76] MAINT: code formatting --- src/graphbin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index 1c9e8d6..60b2bb9 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -7,4 +7,4 @@ __version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" -__status__ = "Production" \ No newline at end of file +__status__ = "Production" From 004731b636b0c4c475ebf867d50381116588a422 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 19 Aug 2024 10:05:07 +0930 Subject: [PATCH 73/76] MAINT: code formatting --- src/graphbin/__init__.py | 2 +- .../{utils => bidirectionalmap}/__init__.py | 0 .../bidirectionalmap/bidirectionalmap.py | 0 src/graphbin/cli.py | 2 +- src/graphbin/{utils => }/graphbin_Canu.py | 6 +-- src/graphbin/{utils => }/graphbin_Flye.py | 6 +-- src/graphbin/{utils => }/graphbin_Func.py | 2 +- src/graphbin/{utils => }/graphbin_MEGAHIT.py | 6 +-- src/graphbin/{utils => }/graphbin_Miniasm.py | 6 +-- src/graphbin/{utils => }/graphbin_SGA.py | 6 +-- src/graphbin/{utils => }/graphbin_SPAdes.py | 6 +-- .../__init__.py | 0 .../{utils => }/labelpropagation/labelprop.py | 0 src/graphbin/{utils => }/parsers/__init__.py | 0 .../{utils => }/parsers/canu_parser.py | 2 +- .../{utils => }/parsers/flye_parser.py | 2 +- .../{utils => }/parsers/megahit_parser.py | 2 +- .../{utils => }/parsers/miniasm_parser.py | 2 +- .../{utils => }/parsers/sga_parser.py | 2 +- .../{utils => }/parsers/spades_parser.py | 2 +- .../bidirectionalmap/bidirectionalmap.py | 42 ------------------- .../visualise_result_Flye_Canu_Miniasm.py | 2 +- .../support/visualise_result_MEGAHIT.py | 2 +- src/graphbin/support/visualise_result_SGA.py | 2 +- .../support/visualise_result_SPAdes.py | 2 +- .../utils/labelpropagation/__init__.py | 0 tests/test_bidirectional_map.py | 2 +- 27 files changed, 32 insertions(+), 74 deletions(-) rename src/graphbin/{utils => bidirectionalmap}/__init__.py (100%) rename src/graphbin/{utils => }/bidirectionalmap/bidirectionalmap.py (100%) rename src/graphbin/{utils => }/graphbin_Canu.py (95%) rename src/graphbin/{utils => }/graphbin_Flye.py (95%) rename src/graphbin/{utils => }/graphbin_Func.py (99%) rename src/graphbin/{utils => }/graphbin_MEGAHIT.py (95%) rename src/graphbin/{utils => }/graphbin_Miniasm.py (95%) rename src/graphbin/{utils => }/graphbin_SGA.py (95%) rename src/graphbin/{utils => }/graphbin_SPAdes.py (95%) rename src/graphbin/{utils/bidirectionalmap => labelpropagation}/__init__.py (100%) rename src/graphbin/{utils => }/labelpropagation/labelprop.py (100%) rename src/graphbin/{utils => }/parsers/__init__.py (100%) rename src/graphbin/{utils => }/parsers/canu_parser.py (98%) rename src/graphbin/{utils => }/parsers/flye_parser.py (99%) rename src/graphbin/{utils => }/parsers/megahit_parser.py (98%) rename src/graphbin/{utils => }/parsers/miniasm_parser.py (98%) rename src/graphbin/{utils => }/parsers/sga_parser.py (98%) rename src/graphbin/{utils => }/parsers/spades_parser.py (99%) delete mode 100644 src/graphbin/support/bidirectionalmap/bidirectionalmap.py delete mode 100644 src/graphbin/utils/labelpropagation/__init__.py diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index 1c9e8d6..60b2bb9 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -7,4 +7,4 @@ __version__ = "1.7.2" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" -__status__ = "Production" \ No newline at end of file +__status__ = "Production" diff --git a/src/graphbin/utils/__init__.py b/src/graphbin/bidirectionalmap/__init__.py similarity index 100% rename from src/graphbin/utils/__init__.py rename to src/graphbin/bidirectionalmap/__init__.py diff --git a/src/graphbin/utils/bidirectionalmap/bidirectionalmap.py b/src/graphbin/bidirectionalmap/bidirectionalmap.py similarity index 100% rename from src/graphbin/utils/bidirectionalmap/bidirectionalmap.py rename to src/graphbin/bidirectionalmap/bidirectionalmap.py diff --git a/src/graphbin/cli.py b/src/graphbin/cli.py index 6647afa..2f62172 100644 --- a/src/graphbin/cli.py +++ b/src/graphbin/cli.py @@ -8,7 +8,7 @@ import click -from graphbin.utils import ( +from graphbin import ( graphbin_Canu, graphbin_Flye, graphbin_MEGAHIT, diff --git a/src/graphbin/utils/graphbin_Canu.py b/src/graphbin/graphbin_Canu.py similarity index 95% rename from src/graphbin/utils/graphbin_Canu.py rename to src/graphbin/graphbin_Canu.py index 1ef8f77..1b92380 100755 --- a/src/graphbin/utils/graphbin_Canu.py +++ b/src/graphbin/graphbin_Canu.py @@ -14,9 +14,9 @@ import logging import time -from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.parsers import get_initial_bin_count -from graphbin.utils.parsers.canu_parser import ( +from graphbin.graphbin_Func import graphbin_main +from graphbin.parsers import get_initial_bin_count +from graphbin.parsers.canu_parser import ( get_initial_binning_result, parse_graph, write_output, diff --git a/src/graphbin/utils/graphbin_Flye.py b/src/graphbin/graphbin_Flye.py similarity index 95% rename from src/graphbin/utils/graphbin_Flye.py rename to src/graphbin/graphbin_Flye.py index b6d44a3..13522ae 100755 --- a/src/graphbin/utils/graphbin_Flye.py +++ b/src/graphbin/graphbin_Flye.py @@ -14,9 +14,9 @@ import logging import time -from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.parsers import get_initial_bin_count -from graphbin.utils.parsers.flye_parser import ( +from graphbin.graphbin_Func import graphbin_main +from graphbin.parsers import get_initial_bin_count +from graphbin.parsers.flye_parser import ( get_initial_binning_result, parse_graph, write_output, diff --git a/src/graphbin/utils/graphbin_Func.py b/src/graphbin/graphbin_Func.py similarity index 99% rename from src/graphbin/utils/graphbin_Func.py rename to src/graphbin/graphbin_Func.py index fc03a7f..57af25e 100644 --- a/src/graphbin/utils/graphbin_Func.py +++ b/src/graphbin/graphbin_Func.py @@ -3,7 +3,7 @@ import logging import sys -from graphbin.utils.labelpropagation.labelprop import LabelProp +from graphbin.labelpropagation.labelprop import LabelProp __author__ = "Vijini Mallawaarachchi" diff --git a/src/graphbin/utils/graphbin_MEGAHIT.py b/src/graphbin/graphbin_MEGAHIT.py similarity index 95% rename from src/graphbin/utils/graphbin_MEGAHIT.py rename to src/graphbin/graphbin_MEGAHIT.py index 238fa43..1d6c421 100755 --- a/src/graphbin/utils/graphbin_MEGAHIT.py +++ b/src/graphbin/graphbin_MEGAHIT.py @@ -14,9 +14,9 @@ import logging import time -from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.parsers import get_initial_bin_count -from graphbin.utils.parsers.megahit_parser import ( +from graphbin.graphbin_Func import graphbin_main +from graphbin.parsers import get_initial_bin_count +from graphbin.parsers.megahit_parser import ( get_contig_descriptors, get_initial_binning_result, parse_graph, diff --git a/src/graphbin/utils/graphbin_Miniasm.py b/src/graphbin/graphbin_Miniasm.py similarity index 95% rename from src/graphbin/utils/graphbin_Miniasm.py rename to src/graphbin/graphbin_Miniasm.py index 90ad17a..dd8728c 100755 --- a/src/graphbin/utils/graphbin_Miniasm.py +++ b/src/graphbin/graphbin_Miniasm.py @@ -14,9 +14,9 @@ import logging import time -from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.parsers import get_initial_bin_count -from graphbin.utils.parsers.miniasm_parser import ( +from graphbin.graphbin_Func import graphbin_main +from graphbin.parsers import get_initial_bin_count +from graphbin.parsers.miniasm_parser import ( get_initial_binning_result, parse_graph, write_output, diff --git a/src/graphbin/utils/graphbin_SGA.py b/src/graphbin/graphbin_SGA.py similarity index 95% rename from src/graphbin/utils/graphbin_SGA.py rename to src/graphbin/graphbin_SGA.py index b377313..81c7e7d 100755 --- a/src/graphbin/utils/graphbin_SGA.py +++ b/src/graphbin/graphbin_SGA.py @@ -14,9 +14,9 @@ import logging import time -from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.parsers import get_initial_bin_count -from graphbin.utils.parsers.sga_parser import ( +from graphbin.graphbin_Func import graphbin_main +from graphbin.parsers import get_initial_bin_count +from graphbin.parsers.sga_parser import ( get_contig_descriptions, get_initial_binning_result, parse_graph, diff --git a/src/graphbin/utils/graphbin_SPAdes.py b/src/graphbin/graphbin_SPAdes.py similarity index 95% rename from src/graphbin/utils/graphbin_SPAdes.py rename to src/graphbin/graphbin_SPAdes.py index d93e9a7..0a67d98 100755 --- a/src/graphbin/utils/graphbin_SPAdes.py +++ b/src/graphbin/graphbin_SPAdes.py @@ -14,9 +14,9 @@ import logging import time -from graphbin.utils.graphbin_Func import graphbin_main -from graphbin.utils.parsers import get_initial_bin_count -from graphbin.utils.parsers.spades_parser import ( +from graphbin.graphbin_Func import graphbin_main +from graphbin.parsers import get_initial_bin_count +from graphbin.parsers.spades_parser import ( get_initial_binning_result, parse_graph, write_output, diff --git a/src/graphbin/utils/bidirectionalmap/__init__.py b/src/graphbin/labelpropagation/__init__.py similarity index 100% rename from src/graphbin/utils/bidirectionalmap/__init__.py rename to src/graphbin/labelpropagation/__init__.py diff --git a/src/graphbin/utils/labelpropagation/labelprop.py b/src/graphbin/labelpropagation/labelprop.py similarity index 100% rename from src/graphbin/utils/labelpropagation/labelprop.py rename to src/graphbin/labelpropagation/labelprop.py diff --git a/src/graphbin/utils/parsers/__init__.py b/src/graphbin/parsers/__init__.py similarity index 100% rename from src/graphbin/utils/parsers/__init__.py rename to src/graphbin/parsers/__init__.py diff --git a/src/graphbin/utils/parsers/canu_parser.py b/src/graphbin/parsers/canu_parser.py similarity index 98% rename from src/graphbin/utils/parsers/canu_parser.py rename to src/graphbin/parsers/canu_parser.py index 233b943..1ee501b 100644 --- a/src/graphbin/utils/parsers/canu_parser.py +++ b/src/graphbin/parsers/canu_parser.py @@ -9,7 +9,7 @@ from cogent3.parse.fasta import MinimalFastaParser from igraph import * -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap +from graphbin.bidirectionalmap.bidirectionalmap import BidirectionalMap __author__ = "Vijini Mallawaarachchi" diff --git a/src/graphbin/utils/parsers/flye_parser.py b/src/graphbin/parsers/flye_parser.py similarity index 99% rename from src/graphbin/utils/parsers/flye_parser.py rename to src/graphbin/parsers/flye_parser.py index c980f86..d427d07 100644 --- a/src/graphbin/utils/parsers/flye_parser.py +++ b/src/graphbin/parsers/flye_parser.py @@ -11,7 +11,7 @@ from cogent3.parse.fasta import MinimalFastaParser from igraph import * -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap +from graphbin.bidirectionalmap.bidirectionalmap import BidirectionalMap __author__ = "Vijini Mallawaarachchi" diff --git a/src/graphbin/utils/parsers/megahit_parser.py b/src/graphbin/parsers/megahit_parser.py similarity index 98% rename from src/graphbin/utils/parsers/megahit_parser.py rename to src/graphbin/parsers/megahit_parser.py index e748e3d..e3cc6d1 100644 --- a/src/graphbin/utils/parsers/megahit_parser.py +++ b/src/graphbin/parsers/megahit_parser.py @@ -10,7 +10,7 @@ from cogent3.parse.fasta import MinimalFastaParser from igraph import * -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap +from graphbin.bidirectionalmap.bidirectionalmap import BidirectionalMap __author__ = "Vijini Mallawaarachchi" diff --git a/src/graphbin/utils/parsers/miniasm_parser.py b/src/graphbin/parsers/miniasm_parser.py similarity index 98% rename from src/graphbin/utils/parsers/miniasm_parser.py rename to src/graphbin/parsers/miniasm_parser.py index 5aec23c..86986f3 100644 --- a/src/graphbin/utils/parsers/miniasm_parser.py +++ b/src/graphbin/parsers/miniasm_parser.py @@ -9,7 +9,7 @@ from cogent3.parse.fasta import MinimalFastaParser from igraph import * -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap +from graphbin.bidirectionalmap.bidirectionalmap import BidirectionalMap __author__ = "Vijini Mallawaarachchi" diff --git a/src/graphbin/utils/parsers/sga_parser.py b/src/graphbin/parsers/sga_parser.py similarity index 98% rename from src/graphbin/utils/parsers/sga_parser.py rename to src/graphbin/parsers/sga_parser.py index d56b950..a8b83f9 100644 --- a/src/graphbin/utils/parsers/sga_parser.py +++ b/src/graphbin/parsers/sga_parser.py @@ -10,7 +10,7 @@ from cogent3.parse.fasta import MinimalFastaParser from igraph import * -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap +from graphbin.bidirectionalmap.bidirectionalmap import BidirectionalMap __author__ = "Vijini Mallawaarachchi" diff --git a/src/graphbin/utils/parsers/spades_parser.py b/src/graphbin/parsers/spades_parser.py similarity index 99% rename from src/graphbin/utils/parsers/spades_parser.py rename to src/graphbin/parsers/spades_parser.py index 0c69ff7..cad36fe 100644 --- a/src/graphbin/utils/parsers/spades_parser.py +++ b/src/graphbin/parsers/spades_parser.py @@ -12,7 +12,7 @@ from cogent3.parse.fasta import MinimalFastaParser from igraph import * -from graphbin.utils.bidirectionalmap.bidirectionalmap import BidirectionalMap +from graphbin.bidirectionalmap.bidirectionalmap import BidirectionalMap __author__ = "Vijini Mallawaarachchi" diff --git a/src/graphbin/support/bidirectionalmap/bidirectionalmap.py b/src/graphbin/support/bidirectionalmap/bidirectionalmap.py deleted file mode 100644 index b07e080..0000000 --- a/src/graphbin/support/bidirectionalmap/bidirectionalmap.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python - -""" -This code is used from the source found at -https://stackoverflow.com/questions/3318625/how-to-implement-an-efficient-bidirectional-hash-table -answered by user jme -""" - - -class BidirectionalError(Exception): - """Must set a unique value in a BijectiveMap.""" - - def __init__(self, value): - self.value = value - msg = 'The value "{}" is already in the mapping.' - super().__init__(msg.format(value)) - - -class BidirectionalMap(dict): - """Invertible map.""" - - def __init__(self, inverse=None): - if inverse is None: - inverse = self.__class__(inverse=self) - self.inverse = inverse - - def __setitem__(self, key, value): - if value in self.inverse: - raise BidirectionalError(value) - - self.inverse._set_item(value, key) - self._set_item(key, value) - - def __delitem__(self, key): - self.inverse._del_item(self[key]) - self._del_item(key) - - def _del_item(self, key): - super().__delitem__(key) - - def _set_item(self, key, value): - super().__setitem__(key, value) diff --git a/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py b/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py index eec576f..bf0ca4c 100644 --- a/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py +++ b/src/graphbin/support/visualise_result_Flye_Canu_Miniasm.py @@ -16,7 +16,7 @@ import subprocess import sys -from bidirectionalmap.bidirectionalmap import BidirectionalMap +from graphbin.bidirectionalmap.bidirectionalmap import BidirectionalMap from igraph import * diff --git a/src/graphbin/support/visualise_result_MEGAHIT.py b/src/graphbin/support/visualise_result_MEGAHIT.py index 33e35fa..22b27a6 100644 --- a/src/graphbin/support/visualise_result_MEGAHIT.py +++ b/src/graphbin/support/visualise_result_MEGAHIT.py @@ -17,7 +17,7 @@ import subprocess import sys -from bidirectionalmap.bidirectionalmap import BidirectionalMap +from graphbin.bidirectionalmap.bidirectionalmap import BidirectionalMap from cogent3.parse.fasta import MinimalFastaParser from igraph import * diff --git a/src/graphbin/support/visualise_result_SGA.py b/src/graphbin/support/visualise_result_SGA.py index 89117a3..36fe66a 100644 --- a/src/graphbin/support/visualise_result_SGA.py +++ b/src/graphbin/support/visualise_result_SGA.py @@ -17,7 +17,7 @@ import subprocess import sys -from bidirectionalmap.bidirectionalmap import BidirectionalMap +from graphbin.bidirectionalmap.bidirectionalmap import BidirectionalMap from igraph import * diff --git a/src/graphbin/support/visualise_result_SPAdes.py b/src/graphbin/support/visualise_result_SPAdes.py index dfdcd9b..353503c 100644 --- a/src/graphbin/support/visualise_result_SPAdes.py +++ b/src/graphbin/support/visualise_result_SPAdes.py @@ -17,7 +17,7 @@ import subprocess import sys -from bidirectionalmap.bidirectionalmap import BidirectionalMap +from graphbin.bidirectionalmap.bidirectionalmap import BidirectionalMap from igraph import * diff --git a/src/graphbin/utils/labelpropagation/__init__.py b/src/graphbin/utils/labelpropagation/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_bidirectional_map.py b/tests/test_bidirectional_map.py index ac52a4c..9382037 100644 --- a/tests/test_bidirectional_map.py +++ b/tests/test_bidirectional_map.py @@ -4,7 +4,7 @@ import pytest -from graphbin.utils.bidirectionalmap.bidirectionalmap import * +from graphbin.bidirectionalmap.bidirectionalmap import * __author__ = "Vijini Mallawaarachchi" From 34c45490bcec6392fd2118fe27589a767af97ae2 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 19 Aug 2024 10:29:21 +0930 Subject: [PATCH 74/76] REL: update version number --- src/graphbin/__init__.py | 2 +- src/graphbin/cli.py | 2 +- src/graphbin/graphbin_Canu.py | 2 +- src/graphbin/graphbin_Flye.py | 2 +- src/graphbin/graphbin_Func.py | 2 +- src/graphbin/graphbin_MEGAHIT.py | 2 +- src/graphbin/graphbin_Miniasm.py | 2 +- src/graphbin/graphbin_SGA.py | 2 +- src/graphbin/graphbin_SPAdes.py | 2 +- src/graphbin/labelpropagation/labelprop.py | 2 +- src/graphbin/parsers/__init__.py | 2 +- src/graphbin/parsers/canu_parser.py | 2 +- src/graphbin/parsers/flye_parser.py | 2 +- src/graphbin/parsers/megahit_parser.py | 2 +- src/graphbin/parsers/miniasm_parser.py | 2 +- src/graphbin/parsers/sga_parser.py | 2 +- src/graphbin/parsers/spades_parser.py | 2 +- tests/test_arguments.py | 2 +- tests/test_bidirectional_map.py | 2 +- tests/test_graphbin.py | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/graphbin/__init__.py b/src/graphbin/__init__.py index 60b2bb9..45216ca 100755 --- a/src/graphbin/__init__.py +++ b/src/graphbin/__init__.py @@ -4,7 +4,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/cli.py b/src/graphbin/cli.py index 2f62172..b39b87b 100644 --- a/src/graphbin/cli.py +++ b/src/graphbin/cli.py @@ -22,7 +22,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/graphbin_Canu.py b/src/graphbin/graphbin_Canu.py index 1b92380..e77f302 100755 --- a/src/graphbin/graphbin_Canu.py +++ b/src/graphbin/graphbin_Canu.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/graphbin_Flye.py b/src/graphbin/graphbin_Flye.py index 13522ae..2250561 100755 --- a/src/graphbin/graphbin_Flye.py +++ b/src/graphbin/graphbin_Flye.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/graphbin_Func.py b/src/graphbin/graphbin_Func.py index 57af25e..e671c79 100644 --- a/src/graphbin/graphbin_Func.py +++ b/src/graphbin/graphbin_Func.py @@ -10,7 +10,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/graphbin_MEGAHIT.py b/src/graphbin/graphbin_MEGAHIT.py index 1d6c421..4157805 100755 --- a/src/graphbin/graphbin_MEGAHIT.py +++ b/src/graphbin/graphbin_MEGAHIT.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/graphbin_Miniasm.py b/src/graphbin/graphbin_Miniasm.py index dd8728c..e4220f3 100755 --- a/src/graphbin/graphbin_Miniasm.py +++ b/src/graphbin/graphbin_Miniasm.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/graphbin_SGA.py b/src/graphbin/graphbin_SGA.py index 81c7e7d..f9b8779 100755 --- a/src/graphbin/graphbin_SGA.py +++ b/src/graphbin/graphbin_SGA.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/graphbin_SPAdes.py b/src/graphbin/graphbin_SPAdes.py index 0a67d98..cb32f03 100755 --- a/src/graphbin/graphbin_SPAdes.py +++ b/src/graphbin/graphbin_SPAdes.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/labelpropagation/labelprop.py b/src/graphbin/labelpropagation/labelprop.py index 8277fd4..87175c5 100644 --- a/src/graphbin/labelpropagation/labelprop.py +++ b/src/graphbin/labelpropagation/labelprop.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Lingzhe Teng", "Vijini Mallawaarachchi"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/parsers/__init__.py b/src/graphbin/parsers/__init__.py index 73645a5..cad4b56 100644 --- a/src/graphbin/parsers/__init__.py +++ b/src/graphbin/parsers/__init__.py @@ -10,7 +10,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/parsers/canu_parser.py b/src/graphbin/parsers/canu_parser.py index 1ee501b..9c76e02 100644 --- a/src/graphbin/parsers/canu_parser.py +++ b/src/graphbin/parsers/canu_parser.py @@ -16,7 +16,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/parsers/flye_parser.py b/src/graphbin/parsers/flye_parser.py index d427d07..a3f1e9d 100644 --- a/src/graphbin/parsers/flye_parser.py +++ b/src/graphbin/parsers/flye_parser.py @@ -18,7 +18,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/parsers/megahit_parser.py b/src/graphbin/parsers/megahit_parser.py index e3cc6d1..d5e06a1 100644 --- a/src/graphbin/parsers/megahit_parser.py +++ b/src/graphbin/parsers/megahit_parser.py @@ -17,7 +17,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/parsers/miniasm_parser.py b/src/graphbin/parsers/miniasm_parser.py index 86986f3..186385f 100644 --- a/src/graphbin/parsers/miniasm_parser.py +++ b/src/graphbin/parsers/miniasm_parser.py @@ -16,7 +16,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/parsers/sga_parser.py b/src/graphbin/parsers/sga_parser.py index a8b83f9..fd05746 100644 --- a/src/graphbin/parsers/sga_parser.py +++ b/src/graphbin/parsers/sga_parser.py @@ -17,7 +17,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/src/graphbin/parsers/spades_parser.py b/src/graphbin/parsers/spades_parser.py index cad36fe..d3bb9c5 100644 --- a/src/graphbin/parsers/spades_parser.py +++ b/src/graphbin/parsers/spades_parser.py @@ -19,7 +19,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Anuradha Wickramarachchi", "Yu Lin"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Production" diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 92147e6..7637121 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Development" diff --git a/tests/test_bidirectional_map.py b/tests/test_bidirectional_map.py index 9382037..014e03e 100644 --- a/tests/test_bidirectional_map.py +++ b/tests/test_bidirectional_map.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Development" diff --git a/tests/test_graphbin.py b/tests/test_graphbin.py index 7adb335..a79afc1 100644 --- a/tests/test_graphbin.py +++ b/tests/test_graphbin.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright 2019-2022, GraphBin Project" __credits__ = ["Vijini Mallawaarachchi", "Gavin Huttley"] __license__ = "BSD-3" -__version__ = "1.7.2" +__version__ = "1.7.3" __maintainer__ = "Vijini Mallawaarachchi" __email__ = "viji.mallawaarachchi@gmail.com" __status__ = "Development" From dc85288269000b1663bd6d9ebcbbe939c0cfca54 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 19 Aug 2024 10:33:27 +0930 Subject: [PATCH 75/76] DOC: update README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 3ef69a5..813b65b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@

- GraphBin logo - GraphBin logo + GraphBin logo

# GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs From 032f2e8bd90d2745cc634a5451c6541fffa207c6 Mon Sep 17 00:00:00 2001 From: Vijini Mallawaarachchi Date: Mon, 19 Aug 2024 10:36:18 +0930 Subject: [PATCH 76/76] DOC: update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 813b65b..3ef69a5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@

- GraphBin logo + GraphBin logo + GraphBin logo

# GraphBin: Refined Binning of Metagenomic Contigs using Assembly Graphs