diff --git a/.github/workflows/build-containers.yml b/.github/workflows/build-containers.yml index 8ebc59435..afdca2850 100644 --- a/.github/workflows/build-containers.yml +++ b/.github/workflows/build-containers.yml @@ -43,6 +43,11 @@ jobs: with: path: docker-wrappers/DOMINO container: reedcompbio/domino + build-and-remove-btb: + uses: "./.github/workflows/build-and-remove-template.yml" + with: + path: docker-wrappers/BowTieBuilder + container: reedcompbio/bowtiebuilder build-and-remove-cytoscape: uses: "./.github/workflows/build-and-remove-template.yml" with: diff --git a/.github/workflows/test-spras.yml b/.github/workflows/test-spras.yml index 4c608cfac..9445aab24 100644 --- a/.github/workflows/test-spras.yml +++ b/.github/workflows/test-spras.yml @@ -61,6 +61,155 @@ jobs: shell: bash --login {0} run: snakemake --cores 2 --configfile config/config.yaml --show-failed-logs + # Builds the Docker images + docker: + name: Build Docker images + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + steps: + - name: Checkout repository + uses: actions/checkout@v2 + # Pull from Docker Hub to use the cache + # https://medium.com/mobileforgood/coding-tips-patterns-for-continuous-integration-with-docker-on-travis-ci-9cedb8348a62 + # https://github.com/docker/build-push-action/issues/7 + - name: Pull Docker images + run: | + docker pull reedcompbio/omics-integrator-1:latest + docker pull reedcompbio/omics-integrator-2:v2 + docker pull reedcompbio/pathlinker:v2 + docker pull reedcompbio/meo:latest + docker pull reedcompbio/mincostflow:latest + docker pull reedcompbio/allpairs:v2 + docker pull reedcompbio/domino:latest + docker pull reedcompbio/py4cytoscape:v3 + docker pull reedcompbio/bowtiebuilder:v2 + docker pull reedcompbio/spras:v0.1.0 + + - name: Build Omics Integrator 1 Docker image + uses: docker/build-push-action@v1 + with: + path: docker-wrappers/OmicsIntegrator1/. + dockerfile: docker-wrappers/OmicsIntegrator1/Dockerfile + repository: reedcompbio/omics-integrator-1 + tags: latest + cache_froms: reedcompbio/omics-integrator-1:latest + push: false + - name: Remove Omics Integrator 1 Docker image + # Remove the image to prevent the cache from being used. Here we use + # `|| true` to prevent the job from failing if the image doesn't exist or + # can't be removed for some reason + run: docker rmi reedcompbio/omics-integrator-1:latest || true + + - name: Build Omics Integrator 2 Docker image + uses: docker/build-push-action@v1 + with: + path: docker-wrappers/OmicsIntegrator2/. + dockerfile: docker-wrappers/OmicsIntegrator2/Dockerfile + repository: reedcompbio/omics-integrator-2 + tags: v2 + cache_froms: reedcompbio/omics-integrator-2:latest + push: false + - name: Remove Omics Integrator 2 Docker image + run: docker rmi reedcompbio/omics-integrator-2:latest || true + + - name: Build PathLinker Docker image + uses: docker/build-push-action@v1 + with: + path: docker-wrappers/PathLinker/. + dockerfile: docker-wrappers/PathLinker/Dockerfile + repository: reedcompbio/pathlinker + tags: v2 + cache_froms: reedcompbio/pathlinker:latest + push: false + - name: Remove PathLinker Docker image + run: docker rmi reedcompbio/pathlinker:latest || true + + - name: Build Maximum Edge Orientation Docker image + uses: docker/build-push-action@v1 + with: + path: docker-wrappers/MEO/. + dockerfile: docker-wrappers/MEO/Dockerfile + repository: reedcompbio/meo + tags: latest + cache_froms: reedcompbio/meo:latest + push: false + - name: Remove MEO Docker image + run: docker rmi reedcompbio/meo:latest || true + + - name: Build MinCostFlow Docker image + uses: docker/build-push-action@v1 + with: + path: docker-wrappers/MinCostFlow/. + dockerfile: docker-wrappers/MinCostFlow/Dockerfile + repository: reedcompbio/mincostflow + tags: latest + cache_froms: reedcompbio/mincostflow:latest + push: false + - name: Remove MinCostFlow Docker image + run: docker rmi reedcompbio/mincostflow:latest || true + + - name: Build All Pairs Shortest Paths Docker image + uses: docker/build-push-action@v1 + with: + path: docker-wrappers/AllPairs/. + dockerfile: docker-wrappers/AllPairs/Dockerfile + repository: reedcompbio/allpairs + tags: v2 + cache_froms: reedcompbio/allpairs:latest + push: false + - name: Remove All Pairs Shortest Paths Docker image + run: docker rmi reedcompbio/allpairs:latest || true + + - name: Build DOMINO Docker image + uses: docker/build-push-action@v1 + with: + path: docker-wrappers/DOMINO/. + dockerfile: docker-wrappers/DOMINO/Dockerfile + repository: reedcompbio/domino + tags: latest + cache_froms: reedcompbio/domino:latest + push: false + - name: Remove DOMINO Docker image + run: docker rmi reedcompbio/domino:latest || true + + - name: Build Cytoscape Docker image + uses: docker/build-push-action@v1 + with: + path: docker-wrappers/Cytoscape/. + dockerfile: docker-wrappers/Cytoscape/Dockerfile + repository: reedcompbio/py4cytoscape + tags: v3 + cache_froms: reedcompbio/py4cytoscape:v3 + push: false + - name: Remove Cytoscape Docker image + run: docker rmi reedcompbio/py4cytoscape:v3 || true + + - name: Build BowTieBuilder Docker Image + uses: docker/build-push-action@v1 + with: + path: docker-wrappers/BowTieBuilder/. + dockerfile: docker-wrappers/BowTieBuilder/Dockerfile + repository: reedcompbio/bowtiebuilder + tags: v2 + cache_froms: reedcompbio/bowtiebuilder:v2 + push: false + - name: Remove BowTieBuilder Docker image + run: docker rmi reedcompbio/bowtiebuilder:v2 || true + + - name: Build SPRAS Docker image + uses: docker/build-push-action@v1 + with: + path: . + dockerfile: docker-wrappers/SPRAS/Dockerfile + repository: reedcompbio/spras + tags: v0.2.0 + cache_froms: reedcompbio/spras:v0.2.0 + push: false + - name: Remove SPRAS Docker image + run: docker rmi reedcompbio/spras:v0.2.0 || true + # Run pre-commit checks on source files pre-commit: name: Run pre-commit checks diff --git a/config/config.yaml b/config/config.yaml index 9fa1054be..9de89e84a 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -45,6 +45,7 @@ container_registry: # careful: too many parameters might make your runs take a long time. algorithms: + - name: "pathlinker" params: include: true @@ -96,6 +97,10 @@ algorithms: slice_threshold: [0.3] module_threshold: [0.05] + - name: "bowtiebuilder" + params: + include: true + # Here we specify which pathways to run and other file location information. # DataLoader.py can currently only load a single dataset diff --git a/docker-wrappers/BowTieBuilder/Dockerfile b/docker-wrappers/BowTieBuilder/Dockerfile new file mode 100644 index 000000000..06606ec93 --- /dev/null +++ b/docker-wrappers/BowTieBuilder/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.8-bullseye + +WORKDIR /btb +RUN wget https://raw.githubusercontent.com/Reed-CompBio/BowTieBuilder-Algorithm/main/btb.py +RUN pip install networkx==2.8 \ No newline at end of file diff --git a/docker-wrappers/BowTieBuilder/README.md b/docker-wrappers/BowTieBuilder/README.md new file mode 100644 index 000000000..d0b80ee76 --- /dev/null +++ b/docker-wrappers/BowTieBuilder/README.md @@ -0,0 +1,15 @@ +# BowTieBuilder Docker image + +A Docker image for [BowTieBuilder](https://github.com/Reed-CompBio/BowTieBuilder-Algorithm) that is available on [DockerHub](https://hub.docker.com/repository/docker/reedcompbio/bowtiebuilder). + +To create the Docker image run: +``` +docker build -t reedcompbio/bowtiebuilder:v2 -f Dockerfile . +``` +from this directory. + +## Original Paper + +The original paper for [BowTieBuilder] can be accessed here: + +Supper, J., Spangenberg, L., Planatscher, H. et al. BowTieBuilder: modeling signal transduction pathways. BMC Syst Biol 3, 67 (2009). https://doi.org/10.1186/1752-0509-3-67 \ No newline at end of file diff --git a/spras/btb.py b/spras/btb.py new file mode 100644 index 000000000..964a2f191 --- /dev/null +++ b/spras/btb.py @@ -0,0 +1,168 @@ +# need to define a new btb class and contain the following functions +# - generate_inputs +# - run +# - parse_output + +import warnings +from pathlib import Path + +import pandas as pd + +from spras.containers import prepare_volume, run_container +from spras.interactome import ( + reinsert_direction_col_undirected, +) +from spras.prm import PRM +from spras.util import add_rank_column, duplicate_edges, raw_pathway_df + +__all__ = ['BowTieBuilder'] + +""" +BTB will construct a BowTie-shaped graph from the provided input file. +BTB works with directed and undirected graphs. +It generates a graph connecting multiple source nodes to multiple target nodes with the minimal number of intermediate nodes as possible. + +Expected raw edge file format: +Interactor1 Interactor2 Weight +""" + +class BowTieBuilder(PRM): + required_inputs = ['sources', 'targets', 'edges'] + + #generate input taken from meo.py beacuse they have same input requirements + @staticmethod + def generate_inputs(data, filename_map): + """ + Access fields from the dataset and write the required input files + @param data: dataset + @param filename_map: a dict mapping file types in the required_inputs to the filename for that type + @return: + """ + for input_type in BowTieBuilder.required_inputs: + if input_type not in filename_map: + raise ValueError(f"{input_type} filename is missing") + + # Get sources and write to file, repeat for targets + # Does not check whether a node is a source and a target + for node_type in ['sources', 'targets']: + nodes = data.request_node_columns([node_type]) + if nodes is None: + raise ValueError(f'No {node_type} found in the node files') + + # TODO test whether this selection is needed, what values could the column contain that we would want to + # include or exclude? + nodes = nodes.loc[nodes[node_type]] + if(node_type == "sources"): + nodes.to_csv(filename_map["sources"], sep= '\t', index=False, columns=['NODEID'], header=False) + elif(node_type == "targets"): + nodes.to_csv(filename_map["targets"], sep= '\t', index=False, columns=['NODEID'], header=False) + + + # Create network file + edges = data.get_interactome() + + # Format into directed graph + # edges = convert_undirected_to_directed(edges) + + edges.to_csv(filename_map["edges"], sep="\t", index=False, + columns=["Interactor1", "Interactor2", "Weight"], + header=False) + + + + # Skips parameter validation step + @staticmethod + def run(sources=None, targets=None, edges=None, output_file=None, container_framework="docker"): + """ + Run BTB with Docker + @param sources: input source file (required) + @param targets: input target file (required) + @param edges: input edge file (required) + @param output_file: path to the output pathway file (required) + @param container_framework: choose the container runtime framework, currently supports "docker" or "singularity" (optional) + """ + + # Tests for pytest (docker container also runs this) + # Testing out here avoids the trouble that container errors provide + + if not sources or not targets or not edges or not output_file: + raise ValueError('Required BowTieBuilder arguments are missing') + + if not Path(sources).exists() or not Path(targets).exists() or not Path(edges).exists(): + raise ValueError('Missing input file') + + # Testing for btb index errors + # It's a bit messy, but it works \_('_')_/ + with open(edges, 'r') as edge_file: + try: + for line in edge_file: + line = line.strip() + line = line.split('\t') + line = line[2] + + except Exception as err: + raise(err) + + work_dir = '/btb' + + # Each volume is a tuple (src, dest) + volumes = list() + + bind_path, source_file = prepare_volume(sources, work_dir) + volumes.append(bind_path) + + bind_path, target_file = prepare_volume(targets, work_dir) + volumes.append(bind_path) + + bind_path, edges_file = prepare_volume(edges, work_dir) + volumes.append(bind_path) + + # Use its --output argument to set the output file prefix to specify an absolute path and prefix + out_dir = Path(output_file).parent + out_dir.mkdir(parents=True, exist_ok=True) + bind_path, mapped_out_dir = prepare_volume(str(out_dir), work_dir) + volumes.append(bind_path) + mapped_out_prefix = mapped_out_dir + '/raw-pathway.txt' # Use posix path inside the container + + command = ['python', + 'btb.py', + '--edges', + edges_file, + '--sources', + source_file, + '--targets', + target_file, + '--output_file', + mapped_out_prefix] + # command = ['ls', '-R'] + + + print('Running BowTieBuilder with arguments: {}'.format(' '.join(command)), flush=True) + + container_suffix = "bowtiebuilder:v2" + out = run_container(container_framework, + container_suffix, + command, + volumes, + work_dir) + print(out) + # Output is already written to raw-pathway.txt file + + + @staticmethod + def parse_output(raw_pathway_file, standardized_pathway_file): + """ + Convert a predicted pathway into the universal format + @param raw_pathway_file: pathway file produced by an algorithm's run function + @param standardized_pathway_file: the same pathway written in the universal format + """ + # What about multiple raw_pathway_files + df = raw_pathway_df(raw_pathway_file, sep='\t', header=0) + if not df.empty: + df = add_rank_column(df) + df = reinsert_direction_col_undirected(df) + df.columns = ['Node1', 'Node2', 'Rank', 'Direction'] + df, has_duplicates = duplicate_edges(df) + if has_duplicates: + print(f"Duplicate edges were removed from {raw_pathway_file}") + df.to_csv(standardized_pathway_file, index=False, sep='\t', header=True) diff --git a/spras/runner.py b/spras/runner.py index 6ef26496e..ff35a73d4 100644 --- a/spras/runner.py +++ b/spras/runner.py @@ -1,5 +1,6 @@ # supported algorithm imports from spras.allpairs import AllPairs as allpairs +from spras.btb import BowTieBuilder as bowtiebuilder from spras.dataset import Dataset from spras.domino import DOMINO as domino from spras.meo import MEO as meo diff --git a/test/BowTieBuilder/__init__.py b/test/BowTieBuilder/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/BowTieBuilder/expected/bidirectional-output.txt b/test/BowTieBuilder/expected/bidirectional-output.txt new file mode 100644 index 000000000..4d722b5d9 --- /dev/null +++ b/test/BowTieBuilder/expected/bidirectional-output.txt @@ -0,0 +1,3 @@ +Node1 Node2 +S1 A +A T1 diff --git a/test/BowTieBuilder/expected/btb-output.txt b/test/BowTieBuilder/expected/btb-output.txt new file mode 100644 index 000000000..b5afacd4c --- /dev/null +++ b/test/BowTieBuilder/expected/btb-output.txt @@ -0,0 +1,5 @@ +Node1 Node2 +S1 A +S2 A +A T1 +A T2 diff --git a/test/BowTieBuilder/expected/disjoint-output.txt b/test/BowTieBuilder/expected/disjoint-output.txt new file mode 100644 index 000000000..27a250c7d --- /dev/null +++ b/test/BowTieBuilder/expected/disjoint-output.txt @@ -0,0 +1,6 @@ +Node1 Node2 +S1 A +S2 C +C T2 +A B +B T1 diff --git a/test/BowTieBuilder/expected/empty-output.txt b/test/BowTieBuilder/expected/empty-output.txt new file mode 100644 index 000000000..9d15ae30c --- /dev/null +++ b/test/BowTieBuilder/expected/empty-output.txt @@ -0,0 +1 @@ +Node1 Node2 diff --git a/test/BowTieBuilder/expected/loop-output.txt b/test/BowTieBuilder/expected/loop-output.txt new file mode 100644 index 000000000..87f007a06 --- /dev/null +++ b/test/BowTieBuilder/expected/loop-output.txt @@ -0,0 +1,6 @@ +Node1 Node2 +S1 A +A B +B T1 +T1 C +C T2 diff --git a/test/BowTieBuilder/expected/source-to-source-disjoint-output.txt b/test/BowTieBuilder/expected/source-to-source-disjoint-output.txt new file mode 100644 index 000000000..67331531d --- /dev/null +++ b/test/BowTieBuilder/expected/source-to-source-disjoint-output.txt @@ -0,0 +1,6 @@ +Node1 Node2 +S1 A +S1 S2 +S2 B +A T1 +B T2 diff --git a/test/BowTieBuilder/expected/source-to-source-output.txt b/test/BowTieBuilder/expected/source-to-source-output.txt new file mode 100644 index 000000000..8f1baa9c4 --- /dev/null +++ b/test/BowTieBuilder/expected/source-to-source-output.txt @@ -0,0 +1,4 @@ +Node1 Node2 +S1 A +A T1 +A T2 diff --git a/test/BowTieBuilder/expected/source-to-source2-output.txt b/test/BowTieBuilder/expected/source-to-source2-output.txt new file mode 100644 index 000000000..4d0c6f22b --- /dev/null +++ b/test/BowTieBuilder/expected/source-to-source2-output.txt @@ -0,0 +1,5 @@ +Node1 Node2 +S1 A +S2 S1 +A T1 +A T2 diff --git a/test/BowTieBuilder/expected/weighted-output.txt b/test/BowTieBuilder/expected/weighted-output.txt new file mode 100644 index 000000000..4d722b5d9 --- /dev/null +++ b/test/BowTieBuilder/expected/weighted-output.txt @@ -0,0 +1,3 @@ +Node1 Node2 +S1 A +A T1 diff --git a/test/BowTieBuilder/input/bad-edges.txt b/test/BowTieBuilder/input/bad-edges.txt new file mode 100644 index 000000000..c08a85035 --- /dev/null +++ b/test/BowTieBuilder/input/bad-edges.txt @@ -0,0 +1,6 @@ +A D 5 +B D 1.3 +C 0.4 +D E 4.5 +D F 2 +D G 3.2 \ No newline at end of file diff --git a/test/BowTieBuilder/input/bidirectional-edges.txt b/test/BowTieBuilder/input/bidirectional-edges.txt new file mode 100644 index 000000000..444ac9b34 --- /dev/null +++ b/test/BowTieBuilder/input/bidirectional-edges.txt @@ -0,0 +1,4 @@ +S1 A 1 +A T1 1 +A S1 1 +T1 A 1 \ No newline at end of file diff --git a/test/BowTieBuilder/input/btb-bad-edges.txt b/test/BowTieBuilder/input/btb-bad-edges.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test/BowTieBuilder/input/btb-edges.txt b/test/BowTieBuilder/input/btb-edges.txt new file mode 100644 index 000000000..e5f85f130 --- /dev/null +++ b/test/BowTieBuilder/input/btb-edges.txt @@ -0,0 +1,5 @@ +S1 A 1 +S1 S2 1 +S2 A 1 +A T1 1 +A T2 1 \ No newline at end of file diff --git a/test/BowTieBuilder/input/btb-sources.txt b/test/BowTieBuilder/input/btb-sources.txt new file mode 100644 index 000000000..052a6f02c --- /dev/null +++ b/test/BowTieBuilder/input/btb-sources.txt @@ -0,0 +1,2 @@ +S1 +S2 \ No newline at end of file diff --git a/test/BowTieBuilder/input/btb-targets.txt b/test/BowTieBuilder/input/btb-targets.txt new file mode 100644 index 000000000..43b435f9b --- /dev/null +++ b/test/BowTieBuilder/input/btb-targets.txt @@ -0,0 +1,2 @@ +T1 +T2 \ No newline at end of file diff --git a/test/BowTieBuilder/input/disjoint-edges.txt b/test/BowTieBuilder/input/disjoint-edges.txt new file mode 100644 index 000000000..b8cb0f460 --- /dev/null +++ b/test/BowTieBuilder/input/disjoint-edges.txt @@ -0,0 +1,5 @@ +S1 A 1 +A B 1 +B T1 1 +S2 C 1 +C T2 1 \ No newline at end of file diff --git a/test/BowTieBuilder/input/disjoint-sources.txt b/test/BowTieBuilder/input/disjoint-sources.txt new file mode 100644 index 000000000..df71e4359 --- /dev/null +++ b/test/BowTieBuilder/input/disjoint-sources.txt @@ -0,0 +1,3 @@ +S1 +S2 +S3 \ No newline at end of file diff --git a/test/BowTieBuilder/input/disjoint-targets.txt b/test/BowTieBuilder/input/disjoint-targets.txt new file mode 100644 index 000000000..f640e8aa0 --- /dev/null +++ b/test/BowTieBuilder/input/disjoint-targets.txt @@ -0,0 +1,3 @@ +T1 +T2 +T3 \ No newline at end of file diff --git a/test/BowTieBuilder/input/disjoint2-edges.txt b/test/BowTieBuilder/input/disjoint2-edges.txt new file mode 100644 index 000000000..2df397828 --- /dev/null +++ b/test/BowTieBuilder/input/disjoint2-edges.txt @@ -0,0 +1,6 @@ +S1 A 1 +A B 1 +B T1 1 +S2 C 1 +C T2 1 +S3 D 1 \ No newline at end of file diff --git a/test/BowTieBuilder/input/edges1.txt b/test/BowTieBuilder/input/edges1.txt new file mode 100644 index 000000000..6f97ec4e4 --- /dev/null +++ b/test/BowTieBuilder/input/edges1.txt @@ -0,0 +1,6 @@ +A D 5 +B D 1.3 +C D 0.4 +D E 4.5 +D F 2 +D G 3.2 \ No newline at end of file diff --git a/test/BowTieBuilder/input/loop-edges.txt b/test/BowTieBuilder/input/loop-edges.txt new file mode 100644 index 000000000..74c9aa802 --- /dev/null +++ b/test/BowTieBuilder/input/loop-edges.txt @@ -0,0 +1,6 @@ +S1 A 1 +A B 1 +B T1 1 +T1 C 1 +C T2 1 +T2 S1 1 \ No newline at end of file diff --git a/test/BowTieBuilder/input/source-to-source-disjoint-edges.txt b/test/BowTieBuilder/input/source-to-source-disjoint-edges.txt new file mode 100644 index 000000000..9c7cec5be --- /dev/null +++ b/test/BowTieBuilder/input/source-to-source-disjoint-edges.txt @@ -0,0 +1,5 @@ +S1 S2 1 +S1 A 1 +A T1 1 +S2 B 1 +B T2 1 \ No newline at end of file diff --git a/test/BowTieBuilder/input/source-to-source-edges.txt b/test/BowTieBuilder/input/source-to-source-edges.txt new file mode 100644 index 000000000..733a09c3d --- /dev/null +++ b/test/BowTieBuilder/input/source-to-source-edges.txt @@ -0,0 +1,4 @@ +S1 A 1 +S1 S2 1 +A T1 1 +A T2 1 \ No newline at end of file diff --git a/test/BowTieBuilder/input/source-to-source2-edges.txt b/test/BowTieBuilder/input/source-to-source2-edges.txt new file mode 100644 index 000000000..f11f86f11 --- /dev/null +++ b/test/BowTieBuilder/input/source-to-source2-edges.txt @@ -0,0 +1,4 @@ +S1 A 1 +S2 S1 1 +A T1 1 +A T2 1 \ No newline at end of file diff --git a/test/LocalNeighborhood/input/ln-nodes.txt b/test/BowTieBuilder/input/source1.txt similarity index 66% rename from test/LocalNeighborhood/input/ln-nodes.txt rename to test/BowTieBuilder/input/source1.txt index 35d242ba7..b1e67221a 100644 --- a/test/LocalNeighborhood/input/ln-nodes.txt +++ b/test/BowTieBuilder/input/source1.txt @@ -1,2 +1,3 @@ A B +C diff --git a/test/BowTieBuilder/input/target-to-source-edges.txt b/test/BowTieBuilder/input/target-to-source-edges.txt new file mode 100644 index 000000000..5f9fc0018 --- /dev/null +++ b/test/BowTieBuilder/input/target-to-source-edges.txt @@ -0,0 +1,2 @@ +A S1 1 +T1 A 1 \ No newline at end of file diff --git a/test/BowTieBuilder/input/target1.txt b/test/BowTieBuilder/input/target1.txt new file mode 100644 index 000000000..0cae3d39a --- /dev/null +++ b/test/BowTieBuilder/input/target1.txt @@ -0,0 +1,3 @@ +E +F +G diff --git a/test/BowTieBuilder/input/weight-one-edges.txt b/test/BowTieBuilder/input/weight-one-edges.txt new file mode 100644 index 000000000..9b3059a13 --- /dev/null +++ b/test/BowTieBuilder/input/weight-one-edges.txt @@ -0,0 +1,4 @@ +S1 A 1 +A T1 1 +S1 B 0.5 +B T1 0.5 \ No newline at end of file diff --git a/test/BowTieBuilder/input/weighted-edges.txt b/test/BowTieBuilder/input/weighted-edges.txt new file mode 100644 index 000000000..76fc0337f --- /dev/null +++ b/test/BowTieBuilder/input/weighted-edges.txt @@ -0,0 +1,4 @@ +S1 A 0.9 +A T1 0.9 +S1 B 0.5 +B T1 0.5 \ No newline at end of file diff --git a/test/BowTieBuilder/test_btb.py b/test/BowTieBuilder/test_btb.py new file mode 100644 index 000000000..88b12d0dd --- /dev/null +++ b/test/BowTieBuilder/test_btb.py @@ -0,0 +1,309 @@ +import sys +from filecmp import cmp +from pathlib import Path + +import pytest + +import spras.config as config + +config.init_from_file("config/config.yaml") + +# TODO consider refactoring to simplify the import +# Modify the path because of the - in the directory +SPRAS_ROOT = Path(__file__).parent.parent.parent.absolute() +sys.path.append(str(Path(SPRAS_ROOT, 'docker-wrappers', 'BowTieBuilder'))) +from spras.btb import BowTieBuilder as BTB + +TEST_DIR = Path('test', 'BowTieBuilder/') +OUT_FILE_DEFAULT = Path(TEST_DIR, 'output', 'raw-pathway.txt') + + +class TestBowTieBuilder: + """ + Run the BowTieBuilder algorithm with missing arguments + """ + def test_btb_missing(self): + with pytest.raises(ValueError): + # No edges + BTB.run( + targets=Path(TEST_DIR, 'input', 'target.txt'), + sources=Path(TEST_DIR, 'input', 'source.txt'), + output_file=OUT_FILE_DEFAULT) + with pytest.raises(ValueError): + # No source + BTB.run( + targets=Path(TEST_DIR, 'input', 'target.txt'), + edges=Path(TEST_DIR, 'input', 'edges.txt'), + output_file=OUT_FILE_DEFAULT) + with pytest.raises(ValueError): + # No target + BTB.run( + sources=Path(TEST_DIR, 'input', 'source.txt'), + edges=Path(TEST_DIR, 'input', 'edges.txt'), + output_file=OUT_FILE_DEFAULT) + + + """ + Run the BowTieBuilder algorithm with missing files + """ + def test_btb_file(self): + with pytest.raises(ValueError): + BTB.run(sources=Path(TEST_DIR, 'input', 'unknown.txt'), + targets=Path(TEST_DIR, 'input', 'target.txt'), + edges=Path(TEST_DIR, 'input', 'edges.txt'), + output_file=OUT_FILE_DEFAULT) + + """ + Run the BowTieBuilder algorithm with bad input data + """ + def test_format_error(self): + with pytest.raises(IndexError): + BTB.run(sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + edges=Path(TEST_DIR, 'input', 'bad-edges.txt'), + output_file=OUT_FILE_DEFAULT) + + """ + Run the BowTieBuilder algorithm on the example input files and check the output matches the expected output + """ + def test_btb(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'btb-edges.txt'), + sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'btb-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' + + """ + Run the BowTieBuilder algorithm on the example disjoint input files and check the output matches the expected output + """ + def test_disjoint(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'disjoint-edges.txt'), + sources=Path(TEST_DIR, 'input', 'disjoint-sources.txt'), + targets=Path(TEST_DIR, 'input', 'disjoint-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'disjoint-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' + + """ + Run the BowTieBuilder algorithm on the example disjoint2 input files and check the output matches the expected output + """ + def test_disjoint2(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'disjoint2-edges.txt'), + sources=Path(TEST_DIR, 'input', 'disjoint-sources.txt'), + targets=Path(TEST_DIR, 'input', 'disjoint-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'disjoint-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' + + """ + Run the BowTieBuilder algorithm with a missing input file + """ + def test_missing_file(self): + with pytest.raises(ValueError): + with pytest.raises(OSError): + BTB.run(edges=Path(TEST_DIR, 'input', 'missing.txt'), + sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + output_file=OUT_FILE_DEFAULT) + + + """ + Run the BowTieBuilder algorithm on the example source to source input files and check the output matches the expected output + """ + def test_source_to_source(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'source-to-source-edges.txt'), + sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'source-to-source-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' + + """ + Run the BowTieBuilder algorithm on the example source to source input files and check the output matches the expected output + """ + def test_source_to_source2(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'source-to-source2-edges.txt'), + sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'source-to-source2-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' + + """ + Run the BowTieBuilder algorithm on two separate source to target paths connected by sources and check the output matches the expected output + """ + + def test_source_to_source_disjoint(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'source-to-source-disjoint-edges.txt'), + sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'source-to-source-disjoint-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' + + """ + Run the BowTieBuilder algorithm on the example bidirectional input files and check the output matches the expected output + """ + + def test_bidirectional(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'bidirectional-edges.txt'), + sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'bidirectional-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' + + """ + Run the BowTieBuilder algorithm on the example target to source input files and check the output matches the expected output + """ + + def test_target_to_source(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'target-to-source-edges.txt'), + sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'empty-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' + + """ + Run the BowTieBuilder algorithm on the example loop network files and check the output matches the expected output + """ + + def test_loop(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'loop-edges.txt'), + sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'loop-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' + + """ + Run the BowTieBuilder algorithm on the weighted input files and check the output matches the expected output + """ + + def test_weighted(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'weighted-edges.txt'), + sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'weighted-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' + + def test_weight_one(self): + OUT_FILE_DEFAULT.unlink(missing_ok=True) + BTB.run(edges=Path(TEST_DIR, 'input', 'weight-one-edges.txt'), + sources=Path(TEST_DIR, 'input', 'btb-sources.txt'), + targets=Path(TEST_DIR, 'input', 'btb-targets.txt'), + output_file=OUT_FILE_DEFAULT) + assert OUT_FILE_DEFAULT.exists(), 'Output file was not written' + expected_file = Path(TEST_DIR, 'expected', 'weighted-output.txt') + + # Read the content of the output files and expected file into sets + with open(OUT_FILE_DEFAULT, 'r') as output_file: + output_content = set(output_file.read().splitlines()) + with open(expected_file, 'r') as expected_output_file: + expected_content = set(expected_output_file.read().splitlines()) + + # Check if the sets are equal, regardless of the order of lines + assert output_content == expected_content, 'Output file does not match expected output file' diff --git a/test/LocalNeighborhood/expected_output/ln-output.txt b/test/LocalNeighborhood/expected_output/ln-output.txt deleted file mode 100644 index 58dc92d99..000000000 --- a/test/LocalNeighborhood/expected_output/ln-output.txt +++ /dev/null @@ -1,3 +0,0 @@ -A|B -C|B -A|E diff --git a/test/LocalNeighborhood/input/ln-bad-network.txt b/test/LocalNeighborhood/input/ln-bad-network.txt deleted file mode 100644 index 970b0e116..000000000 --- a/test/LocalNeighborhood/input/ln-bad-network.txt +++ /dev/null @@ -1,5 +0,0 @@ -A|B|E -C|B -C|D -D|E -A|E diff --git a/test/LocalNeighborhood/input/ln-network.txt b/test/LocalNeighborhood/input/ln-network.txt deleted file mode 100644 index 5a9b04517..000000000 --- a/test/LocalNeighborhood/input/ln-network.txt +++ /dev/null @@ -1,5 +0,0 @@ -A|B -C|B -C|D -D|E -A|E diff --git a/test/LocalNeighborhood/test_ln.py b/test/LocalNeighborhood/test_ln.py deleted file mode 100644 index 391c5fb15..000000000 --- a/test/LocalNeighborhood/test_ln.py +++ /dev/null @@ -1,52 +0,0 @@ -import sys -from filecmp import cmp -from pathlib import Path - -import pytest - -import spras.config as config - -config.init_from_file("config/config.yaml") - -# TODO consider refactoring to simplify the import -# Modify the path because of the - in the directory -SPRAS_ROOT = Path(__file__).parent.parent.parent.absolute() -sys.path.append(str(Path(SPRAS_ROOT, 'docker-wrappers', 'LocalNeighborhood'))) -from local_neighborhood import local_neighborhood - -TEST_DIR = Path('test', 'LocalNeighborhood/') -OUT_FILE = Path(TEST_DIR, 'output', 'ln-output.txt') - - -class TestLocalNeighborhood: - """ - Run the local neighborhood algorithm on the example input files and check the output matches the expected output - """ - def test_ln(self): - OUT_FILE.unlink(missing_ok=True) - local_neighborhood(network_file=Path(TEST_DIR, 'input', 'ln-network.txt'), - nodes_file=Path(TEST_DIR, 'input', 'ln-nodes.txt'), - output_file=OUT_FILE) - assert OUT_FILE.exists(), 'Output file was not written' - expected_file = Path(TEST_DIR, 'expected_output', 'ln-output.txt') - assert cmp(OUT_FILE, expected_file, shallow=False), 'Output file does not match expected output file' - - """ - Run the local neighborhood algorithm with a missing input file - """ - def test_missing_file(self): - with pytest.raises(OSError): - local_neighborhood(network_file=Path(TEST_DIR, 'input', 'missing.txt'), - nodes_file=Path(TEST_DIR, 'input', 'ln-nodes.txt'), - output_file=OUT_FILE) - - """ - Run the local neighborhood algorithm with an improperly formatted network file - """ - def test_format_error(self): - with pytest.raises(ValueError): - local_neighborhood(network_file=Path(TEST_DIR, 'input', 'ln-bad-network.txt'), - nodes_file=Path(TEST_DIR, 'input', 'ln-nodes.txt'), - output_file=OUT_FILE) - - # Write tests for the Local Neighborhood run function here diff --git a/test/generate-inputs/expected/bowtiebuilder-edges-expected.txt b/test/generate-inputs/expected/bowtiebuilder-edges-expected.txt new file mode 100644 index 000000000..8334ffd53 --- /dev/null +++ b/test/generate-inputs/expected/bowtiebuilder-edges-expected.txt @@ -0,0 +1,2 @@ +test_A B 0.98 +B C 0.77 diff --git a/test/generate-inputs/test_generate_inputs.py b/test/generate-inputs/test_generate_inputs.py index 6d732d315..b7b43a7c1 100644 --- a/test/generate-inputs/test_generate_inputs.py +++ b/test/generate-inputs/test_generate_inputs.py @@ -16,8 +16,9 @@ 'omicsintegrator2': 'edges', 'domino': 'network', 'pathlinker': 'network', - 'allpairs': 'network' -} + 'allpairs': 'network', + 'bowtiebuilder': 'edges' + } class TestGenerateInputs: diff --git a/test/parse-outputs/expected/bowtiebuilder-pathway-expected.txt b/test/parse-outputs/expected/bowtiebuilder-pathway-expected.txt new file mode 100644 index 000000000..21768464c --- /dev/null +++ b/test/parse-outputs/expected/bowtiebuilder-pathway-expected.txt @@ -0,0 +1,3 @@ +Node1 Node2 Rank Direction +A B 1 U +B C 1 U diff --git a/test/parse-outputs/input/bowtiebuilder-raw-pathway.txt b/test/parse-outputs/input/bowtiebuilder-raw-pathway.txt new file mode 100644 index 000000000..d92837ade --- /dev/null +++ b/test/parse-outputs/input/bowtiebuilder-raw-pathway.txt @@ -0,0 +1,3 @@ +Node1 Node2 +A B +B C diff --git a/test/parse-outputs/input/duplicate-edges/bowtiebuilder-raw-pathway.txt b/test/parse-outputs/input/duplicate-edges/bowtiebuilder-raw-pathway.txt new file mode 100644 index 000000000..279603e69 --- /dev/null +++ b/test/parse-outputs/input/duplicate-edges/bowtiebuilder-raw-pathway.txt @@ -0,0 +1,5 @@ +Node1 Node2 +A B +B C +A B +B C diff --git a/test/parse-outputs/test_parse_outputs.py b/test/parse-outputs/test_parse_outputs.py index 49baf10f8..979c1c091 100644 --- a/test/parse-outputs/test_parse_outputs.py +++ b/test/parse-outputs/test_parse_outputs.py @@ -12,7 +12,7 @@ # the DOMINO output of the network dip.sif and the nodes tnfa_active_genes_file.txt # from https://github.com/Shamir-Lab/DOMINO/tree/master/examples -algorithms = ['mincostflow', 'meo', 'omicsintegrator1', 'omicsintegrator2', 'pathlinker', 'allpairs', 'domino'] +algorithms = ['mincostflow', 'meo', 'omicsintegrator1', 'omicsintegrator2', 'pathlinker', 'allpairs', 'domino', 'bowtiebuilder'] class TestParseOutputs: