diff --git a/.github/workflows/test-spras.yml b/.github/workflows/test-spras.yml index 177401dd..486653f3 100644 --- a/.github/workflows/test-spras.yml +++ b/.github/workflows/test-spras.yml @@ -52,7 +52,7 @@ jobs: uses: eWaterCycle/setup-apptainer@v2 with: # Choose version from https://github.com/apptainer/apptainer/releases - apptainer-version: 1.1.3 + apptainer-version: 1.2.2 - name: Run tests shell: bash --login {0} # Verbose output and disable stdout and stderr capturing @@ -146,6 +146,15 @@ jobs: tags: latest cache_froms: reedcompbio/domino:latest push: false + - 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: v1 + cache_froms: reedcompbio/py4cytoscape:v1 + push: false # Run pre-commit checks on source files pre-commit: diff --git a/Snakefile b/Snakefile index ff680f60..836e67e8 100644 --- a/Snakefile +++ b/Snakefile @@ -4,7 +4,7 @@ import shutil import yaml from src.dataset import Dataset from src.util import process_config -from src.analysis import ml, summary, graphspace +from src.analysis import ml, summary, graphspace, cytoscape # Snakemake updated the behavior in the 6.5.0 release https://github.com/snakemake/snakemake/pull/1037 # and using the wrong separator prevents Snakemake from matching filenames to the rules that can produce them @@ -68,7 +68,10 @@ def make_final_input(wildcards): # add graph and style JSON files. final_input.extend(expand('{out_dir}{sep}{dataset}-{algorithm_params}{sep}gs.json',out_dir=out_dir,sep=SEP,dataset=dataset_labels,algorithm_params=algorithms_with_params)) final_input.extend(expand('{out_dir}{sep}{dataset}-{algorithm_params}{sep}gsstyle.json',out_dir=out_dir,sep=SEP,dataset=dataset_labels,algorithm_params=algorithms_with_params)) - + + if config["analysis"]["cytoscape"]["include"]: + final_input.extend(expand('{out_dir}{sep}{dataset}-cytoscape.cys',out_dir=out_dir,sep=SEP,dataset=dataset_labels)) + if config["analysis"]["ml"]["include"]: final_input.extend(expand('{out_dir}{sep}{dataset}-pca.png',out_dir=out_dir,sep=SEP,dataset=dataset_labels,algorithm_params=algorithms_with_params)) final_input.extend(expand('{out_dir}{sep}{dataset}-pca-variance.txt',out_dir=out_dir,sep=SEP,dataset=dataset_labels,algorithm_params=algorithms_with_params)) @@ -225,7 +228,7 @@ rule summarize_pathway: run: summary.run(input.standardized_file,output.summary_file,directed=algorithm_directed[wildcards.algorithm]) -# Write GraphSpace JSON Graphs +# Write GraphSpace JSON graphs rule viz_graphspace: input: standardized_file = SEP.join([out_dir, '{dataset}-{algorithm}-{params}', 'pathway.txt']) output: @@ -234,6 +237,16 @@ rule viz_graphspace: run: graphspace.write_json(input.standardized_file,output.graph_json,output.style_json,directed=algorithm_directed[wildcards.algorithm]) + +# Write a Cytoscape session file with all pathways for each dataset +rule viz_cytoscape: + input: pathways = expand('{out_dir}{sep}{{dataset}}-{algorithm_params}{sep}pathway.txt', out_dir=out_dir, sep=SEP, algorithm_params=algorithms_with_params) + output: + session = SEP.join([out_dir, '{dataset}-cytoscape.cys']) + run: + cytoscape.run_cytoscape(input.pathways, output.session, SINGULARITY) + + # Write a single summary table for all pathways for each dataset rule summary_table: input: diff --git a/config/config.yaml b/config/config.yaml index 12718d17..ccf6c46e 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -129,6 +129,9 @@ # Create output files for each pathway that can be visualized with GraphSpace graphspace: include: true + # Create Cytoscape session file with all pathway graphs for each dataset + cytoscape: + include: true # Machine learning analysis (e.g. clustering) of the pathway output files for each dataset ml: include: true diff --git a/config/egfr.yaml b/config/egfr.yaml index a771a602..0fef0f5f 100644 --- a/config/egfr.yaml +++ b/config/egfr.yaml @@ -88,6 +88,8 @@ reconstruction_settings: analysis: graphspace: include: false + cytoscape: + include: true summary: include: true ml: diff --git a/docker-wrappers/Cytoscape/Cytoscape.vmoptions b/docker-wrappers/Cytoscape/Cytoscape.vmoptions new file mode 100644 index 00000000..2e23cf89 --- /dev/null +++ b/docker-wrappers/Cytoscape/Cytoscape.vmoptions @@ -0,0 +1,4 @@ +-Xms256m +-Xmx2g +-Xss5m +-Duser.home=/spras diff --git a/docker-wrappers/Cytoscape/Dockerfile b/docker-wrappers/Cytoscape/Dockerfile new file mode 100644 index 00000000..30837347 --- /dev/null +++ b/docker-wrappers/Cytoscape/Dockerfile @@ -0,0 +1,31 @@ +# Dockerfile derived from +# https://github.com/cytoscape/docker-cytoscape-desktop/blob/173ab46b4b5e5c148113ad0c9960a6af3fc50432/py4cytoscape/Dockerfile +# by Kozo Nishida +FROM python:3.9.13 + +# Versions +ENV CYTOSCAPE_VERSION=3.9.1 +ENV PY4CYTOSCAPE_VERSION=1.3.0 +ENV JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64" +# Workaround for java.util.zip.ZipException: Invalid CEN header https://cytoscape.org/common_issues.html#zipvalidation +ENV EXTRA_JAVA_OPTS="-Djdk.util.zip.disableZip64ExtraFieldValidation=true" + +WORKDIR /py4cytoscape + +# Install py4cytoscape +RUN pip install py4cytoscape==${PY4CYTOSCAPE_VERSION} + +# Install Java and Cytoscape +RUN apt-get update && apt-get -y install default-jdk xvfb supervisor wget +RUN wget https://github.com/cytoscape/cytoscape/releases/download/${CYTOSCAPE_VERSION}/cytoscape-unix-${CYTOSCAPE_VERSION}.tar.gz \ + && tar xf cytoscape-unix-${CYTOSCAPE_VERSION}.tar.gz && rm cytoscape-unix-${CYTOSCAPE_VERSION}.tar.gz +RUN cd cytoscape-unix-${CYTOSCAPE_VERSION}/framework/system/org/cytoscape/property-impl/${CYTOSCAPE_VERSION} \ + && jar -xf property-impl-${CYTOSCAPE_VERSION}.jar cytoscape3.props \ + && cat cytoscape3.props | sed "s/^cyrest.version.*/cyrest.version=3.12.3/g" > cytoscape3.props.tmp \ + && mv cytoscape3.props.tmp cytoscape3.props \ + && jar -uf property-impl-${CYTOSCAPE_VERSION}.jar cytoscape3.props \ + && rm cytoscape3.props + +COPY Cytoscape.vmoptions ./cytoscape-unix-${CYTOSCAPE_VERSION}/Cytoscape.vmoptions +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY cytoscape_util.py . diff --git a/docker-wrappers/Cytoscape/README.md b/docker-wrappers/Cytoscape/README.md new file mode 100644 index 00000000..6f97eb0a --- /dev/null +++ b/docker-wrappers/Cytoscape/README.md @@ -0,0 +1,25 @@ +# Cytoscape image + +A Docker image for [Cytoscape](https://cytoscape.org/) that is available on [DockerHub](https://hub.docker.com/repository/docker/reedcompbio/py4cytoscape). +It was originally derived from the [`docker-cytoscape-desktop/py4cytoscape`](https://github.com/cytoscape/docker-cytoscape-desktop/blob/173ab46b4b5e5c148113ad0c9960a6af3fc50432/py4cytoscape/Dockerfile) image. + +Thank you to Scooter Morris for help debugging problems running Cytoscape in Singularity. + +## Building the Docker image + +To create the Docker image run: +``` +docker build -t reedcompbio/py4cytoscape -f Dockerfile . +``` +from this directory. + +## Testing +Test code is located in `test/analysis/test_cytoscape.py`. +The Docker wrapper can be tested with `pytest`. + +## Versions: +- v1: Use supervisord to launch Cytoscape from a Python subprocess, then connect to Cytoscape with py4cytoscape. Only loads undirected pathways. Compatible with Singularity in local testing (Apptainer version 1.2.2-1.el7) but fails in GitHub Actions. + +## TODO +- Add an auth file for `xvfb-run` +- Java initial heap size, maximum Java heap size, and thread stack size are hard-coded in `Cytoscape.vmoptions` file diff --git a/docker-wrappers/Cytoscape/cytoscape_util.py b/docker-wrappers/Cytoscape/cytoscape_util.py new file mode 100644 index 00000000..1f386353 --- /dev/null +++ b/docker-wrappers/Cytoscape/cytoscape_util.py @@ -0,0 +1,135 @@ +import argparse +import subprocess +import time +from typing import List + +import py4cytoscape as p4c +from requests.exceptions import RequestException + +SLEEP_INTERVAL = 10 +MAX_CONNECTION_ATTEMPTS = 20 + + +def get_parser() -> argparse.ArgumentParser: + """ + :return: an argparse ArgumentParser object for parsing command + line parameters + """ + parser = argparse.ArgumentParser( + description='Visualize pathway files from SPRAS.') + + parser.add_argument( + "--pathway", + dest='pathways', + type=str, + action='append', + required=True, + help='The path to a pathway file. Add the argument multiple times to visualize multiple pathways. ' + 'Optionally use a | to append a label for the pathway such as path/to/file.txt|pathway_label' + ) + + parser.add_argument( + "--output", + dest='output', + type=str, + default='cytoscape-session.cys', + help='The output filename of the Cytoscape session file, which will have the extension .cys added if it is not ' + 'already provided. Default: cytoscape-session.cys' + ) + return parser + + +def parse_arguments() -> argparse.Namespace: + """ + Initialize a parser and use it to parse the command line arguments + :return: parsed dictionary of command line arguments + """ + parser = get_parser() + opts = parser.parse_args() + + return opts + + +def start_remote_cytoscape() -> None: + """ + Use supervisord to start the Cytoscape process. Ping Cytoscape until a connection is established and sleep in + between pings. Raises an error if Cytoscape cannot be reached within the maximum number of attempts. + """ + try: + subprocess.run([ + '/usr/bin/supervisord', '-c', '/etc/supervisor/conf.d/supervisord.conf' + ], + check=True) + except subprocess.CalledProcessError as e: + raise RuntimeError('An error has occurred while trying to run Cytoscape') from e + + connected = False + attempts = 0 + # Allow initial time to start up before trying to connect + time.sleep(SLEEP_INTERVAL) + while not connected and attempts < MAX_CONNECTION_ATTEMPTS: + attempts += 1 + try: + p4c.cytoscape_ping() + print('Connected to Cytoscape', flush=True) + connected = True + except (RequestException, p4c.exceptions.CyError): + print('Pinging Cytoscape, waiting for connection... ', flush=True) + time.sleep(SLEEP_INTERVAL) + pass + except Exception as e: + print(e) + print('Pinging Cytoscape, waiting for connection... ', flush=True) + time.sleep(SLEEP_INTERVAL) + + if not connected: + raise ConnectionError('Could not connect to Cytoscape') + + +def parse_name(pathway: str) -> (str, str): + """ + Extract the optional label from the pathway argument + @param pathway: the command line pathway argument, which may contain a | separated label + @return: a tuple with the file path and the label + """ + parts = pathway.split('|') + # No label provided or empty label provided so the file path is the label + if len(parts) == 1 or len(parts[1]) == 0: + return parts[0], parts[0] + # A valid label was provided + else: + return parts[0], parts[1] + + +def load_pathways(pathways: List[str], output: str) -> None: + """ + Launch and connect to Cytoscape, import all pathways, and save a session file + @param pathways: the list of pathways to import + @param output: the name of the Cytoscape session file to save + """ + if len(pathways) == 0: + raise ValueError('One or more pathway files are required') + + start_remote_cytoscape() + for pathway in pathways: + path, name = parse_name(pathway) + suid = p4c.networks.import_network_from_tabular_file( + file=path, + column_type_list='s,t,x', + delimiters='\t' + ) + p4c.networks.rename_network(name, network=suid) + + p4c.session.save_session(output) + + +def main(): + """ + Main function + """ + opts = parse_arguments() + load_pathways(opts.pathways, opts.output) + + +if __name__ == '__main__': + main() diff --git a/docker-wrappers/Cytoscape/supervisord.conf b/docker-wrappers/Cytoscape/supervisord.conf new file mode 100644 index 00000000..4337dbb9 --- /dev/null +++ b/docker-wrappers/Cytoscape/supervisord.conf @@ -0,0 +1,13 @@ +[supervisord] +nodaemon=false + +[program:cytoscape] +# supervisord syntax for environment variable substitution http://supervisord.org/configuration.html#environment-variables +# The variable is CYTOSCAPE_VERSION +# Removed auth file from the original example because the file used was not present anyway +# Should consider adding back an auth file +# --auth-file /root/.Xauth +command=/bin/bash -c 'xvfb-run -s "-screen 0 1920x1080x24" /py4cytoscape/cytoscape-unix-%(ENV_CYTOSCAPE_VERSION)s/cytoscape.sh' +priority=10 +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 diff --git a/src/analysis/cytoscape.py b/src/analysis/cytoscape.py new file mode 100644 index 00000000..df533784 --- /dev/null +++ b/src/analysis/cytoscape.py @@ -0,0 +1,60 @@ +from pathlib import Path, PurePath +from shutil import rmtree +from typing import List, Union + +from src.util import prepare_volume, run_container + + +def run_cytoscape(pathways: List[Union[str, PurePath]], output_file: str, singularity: bool = False) -> None: + """ + Create a Cytoscape session file with visualizations of each of the provided pathways + @param pathways: a list of pathways to visualize + @param output_file: the output Cytoscape session file + @param singularity: whether to run in a Singularity container + """ + work_dir = '/spras' + + # To work with Singularity, /spras must be mapped to a writeable location because that directory is fixed as + # the home directory inside the container and Cytoscape writes configuration files there + # $HOME cannot be set in the Dockerfile because Singularity overwrites home at launch + env = f'HOME={work_dir}' + + # Each volume is a tuple (src, dest) + volumes = list() + + # A temporary directory for Cytoscape output files + cytoscape_output_dir = Path(output_file.replace('.cys', '')).absolute() + cytoscape_output_dir.mkdir(parents=True, exist_ok=True) + + # TODO update to the latest p4cytoscape and use env variable to control the log directory instead + # Requires generalizing the run_container function to support multiple environment variables + volumes.append((cytoscape_output_dir, PurePath(work_dir, 'logs'))) + # Only needed when running in Singularity + volumes.append((cytoscape_output_dir, PurePath(work_dir, 'CytoscapeConfiguration'))) + + # Map the output file + bind_path, mapped_output = prepare_volume(output_file, work_dir) + volumes.append(bind_path) + + # Create the initial Python command to run inside the container + command = ['python', '/py4cytoscape/cytoscape_util.py', '--output', mapped_output] + + # Map the pathway filenames and add them to the Python command + for pathway in pathways: + bind_path, mapped_pathway = prepare_volume(pathway, work_dir) + volumes.append(bind_path) + # Provided the mapped pathway file path and the original file path as the label Cytoscape + command.extend(['--pathway', f'{mapped_pathway}|{pathway}']) + + print('Running Cytoscape with arguments: {}'.format(' '.join(command)), flush=True) + + # TODO consider making this a string in the config file instead of a Boolean + container_framework = 'singularity' if singularity else 'docker' + out = run_container(container_framework, + 'reedcompbio/py4cytoscape:v1', + command, + volumes, + work_dir, + env) + print(out) + rmtree(cytoscape_output_dir) diff --git a/src/util.py b/src/util.py index 05a35a01..50f297e1 100644 --- a/src/util.py +++ b/src/util.py @@ -53,6 +53,8 @@ def convert_docker_path(src_path: PurePath, dest_path: PurePath, file_path: Unio # TODO consider a better default environment variable +# TODO environment currently a single string (e.g. 'TMPDIR=/OmicsIntegrator1'), should it be a list? +# run_container_singularity assumes a single string # Follow docker-py's naming conventions (https://docker-py.readthedocs.io/en/stable/containers.html) # Technically the argument is an image, not a container, but we use container here. def run_container(framework: str, container: str, command: List[str], volumes: List[Tuple[PurePath, PurePath]], working_dir: str, environment: str = 'SPRAS=True'): @@ -63,7 +65,7 @@ def run_container(framework: str, container: str, command: List[str], volumes: L @param command: command to run in the container @param volumes: a list of volumes to mount where each item is a (source, destination) tuple @param working_dir: the working directory in the container - @param environment: environment variables to set in the container + @param environment: environment variable to set in the container @return: output from Singularity execute or Docker run """ normalized_framework = framework.casefold() @@ -76,7 +78,6 @@ def run_container(framework: str, container: str, command: List[str], volumes: L # TODO any issue with creating a new client each time inside this function? -# TODO environment currently a single string (e.g. 'TMPDIR=/OmicsIntegrator1'), should it be a list? def run_container_docker(container: str, command: List[str], volumes: List[Tuple[PurePath, PurePath]], working_dir: str, environment: str = 'SPRAS=True'): """ Runs a command in the container using Docker. @@ -87,7 +88,7 @@ def run_container_docker(container: str, command: List[str], volumes: List[Tuple @param command: command to run in the container @param volumes: a list of volumes to mount where each item is a (source, destination) tuple @param working_dir: the working directory in the container - @param environment: environment variables to set in the container + @param environment: environment variable to set in the container @return: output from Docker run """ out = None @@ -175,7 +176,7 @@ def run_container_singularity(container: str, command: List[str], volumes: List[ @param command: command to run in the container @param volumes: a list of volumes to mount where each item is a (source, destination) tuple @param working_dir: the working directory in the container - @param environment: environment variables to set in the container + @param environment: environment variable to set in the container @return: output from Singularity execute """ # spython is not compatible with Windows @@ -188,7 +189,15 @@ def run_container_singularity(container: str, command: List[str], volumes: List[ bind_paths = [f'{prepare_path_docker(src)}:{dest}' for src, dest in volumes] # TODO is try/finally needed for Singularity? - singularity_options = ['--cleanenv', '--containall', '--pwd', working_dir, '--env', environment] + singularity_options = ['--cleanenv', '--containall', '--pwd', working_dir] + # Singularity does not allow $HOME to be set as a regular environment variable + # Capture it and use the special argument instead + if environment.startswith('HOME='): + home_dir = environment[5:] + singularity_options.extend(['--home', home_dir]) + else: + singularity_options.extend(['--env', environment]) + # To debug a container add the execute arguments: singularity_options=['--debug'], quiet=False # Adding 'docker://' to the container indicates this is a Docker image Singularity must convert return Client.execute('docker://' + container, diff --git a/test/analysis/input/example/data0-meo-params-GKEDDFZ_pathway.txt b/test/analysis/input/example/data0-meo-params-GKEDDFZ_pathway.txt index 36aa6c96..a22a98ae 100644 --- a/test/analysis/input/example/data0-meo-params-GKEDDFZ_pathway.txt +++ b/test/analysis/input/example/data0-meo-params-GKEDDFZ_pathway.txt @@ -1,2 +1,2 @@ -A B 1 -B C 1 +A B 1 +B C 1 diff --git a/test/analysis/input/example/data0-omicsintegrator1-params-RQCQ4YN_pathway.txt b/test/analysis/input/example/data0-omicsintegrator1-params-RQCQ4YN_pathway.txt index 36aa6c96..a22a98ae 100644 --- a/test/analysis/input/example/data0-omicsintegrator1-params-RQCQ4YN_pathway.txt +++ b/test/analysis/input/example/data0-omicsintegrator1-params-RQCQ4YN_pathway.txt @@ -1,2 +1,2 @@ -A B 1 -B C 1 +A B 1 +B C 1 diff --git a/test/analysis/input/example/data0-omicsintegrator1-params-WY4V42C_pathway.txt b/test/analysis/input/example/data0-omicsintegrator1-params-WY4V42C_pathway.txt index 36aa6c96..a22a98ae 100644 --- a/test/analysis/input/example/data0-omicsintegrator1-params-WY4V42C_pathway.txt +++ b/test/analysis/input/example/data0-omicsintegrator1-params-WY4V42C_pathway.txt @@ -1,2 +1,2 @@ -A B 1 -B C 1 +A B 1 +B C 1 diff --git a/test/analysis/input/example/data0-omicsintegrator2-params-IV3IPCJ_pathway.txt b/test/analysis/input/example/data0-omicsintegrator2-params-IV3IPCJ_pathway.txt index a9c7f4e8..e8a51483 100644 --- a/test/analysis/input/example/data0-omicsintegrator2-params-IV3IPCJ_pathway.txt +++ b/test/analysis/input/example/data0-omicsintegrator2-params-IV3IPCJ_pathway.txt @@ -1,2 +1,2 @@ -B A 1 -B C 1 +B A 1 +B C 1 diff --git a/test/analysis/input/example/data0-pathlinker-params-6SWY7JS_pathway.txt b/test/analysis/input/example/data0-pathlinker-params-6SWY7JS_pathway.txt index 36aa6c96..a22a98ae 100644 --- a/test/analysis/input/example/data0-pathlinker-params-6SWY7JS_pathway.txt +++ b/test/analysis/input/example/data0-pathlinker-params-6SWY7JS_pathway.txt @@ -1,2 +1,2 @@ -A B 1 -B C 1 +A B 1 +B C 1 diff --git a/test/analysis/input/example/data0-pathlinker-params-VQL7BDZ_pathway.txt b/test/analysis/input/example/data0-pathlinker-params-VQL7BDZ_pathway.txt index 36aa6c96..a22a98ae 100644 --- a/test/analysis/input/example/data0-pathlinker-params-VQL7BDZ_pathway.txt +++ b/test/analysis/input/example/data0-pathlinker-params-VQL7BDZ_pathway.txt @@ -1,2 +1,2 @@ -A B 1 -B C 1 +A B 1 +B C 1 diff --git a/test/analysis/input/example/data1-meo-params-GKEDDFZ_pathway.txt b/test/analysis/input/example/data1-meo-params-GKEDDFZ_pathway.txt index dcb52861..d9f812e3 100644 --- a/test/analysis/input/example/data1-meo-params-GKEDDFZ_pathway.txt +++ b/test/analysis/input/example/data1-meo-params-GKEDDFZ_pathway.txt @@ -1,4 +1,4 @@ -A B 1 -B C 1 -A D 1 -C D 1 +A B 1 +B C 1 +A D 1 +C D 1 diff --git a/test/analysis/input/example/data1-omicsintegrator1-params-JAZWLAK_pathway.txt b/test/analysis/input/example/data1-omicsintegrator1-params-JAZWLAK_pathway.txt index b0ddaece..f849b413 100644 --- a/test/analysis/input/example/data1-omicsintegrator1-params-JAZWLAK_pathway.txt +++ b/test/analysis/input/example/data1-omicsintegrator1-params-JAZWLAK_pathway.txt @@ -1,3 +1,3 @@ -A D 1 -G H 1 -G I 1 +A D 1 +G H 1 +G I 1 diff --git a/test/analysis/input/example/data1-omicsintegrator1-params-PU62FNV_pathway.txt b/test/analysis/input/example/data1-omicsintegrator1-params-PU62FNV_pathway.txt index b0ddaece..f849b413 100644 --- a/test/analysis/input/example/data1-omicsintegrator1-params-PU62FNV_pathway.txt +++ b/test/analysis/input/example/data1-omicsintegrator1-params-PU62FNV_pathway.txt @@ -1,3 +1,3 @@ -A D 1 -G H 1 -G I 1 +A D 1 +G H 1 +G I 1 diff --git a/test/analysis/input/example/data1-omicsintegrator2-params-IV3IPCJ_pathway.txt b/test/analysis/input/example/data1-omicsintegrator2-params-IV3IPCJ_pathway.txt index cb205a1b..a4a5dec2 100644 --- a/test/analysis/input/example/data1-omicsintegrator2-params-IV3IPCJ_pathway.txt +++ b/test/analysis/input/example/data1-omicsintegrator2-params-IV3IPCJ_pathway.txt @@ -1,6 +1,6 @@ -C D 1 -C F 1 -A D 1 -I G 1 -H G 1 -G F 1 +C D 1 +C F 1 +A D 1 +I G 1 +H G 1 +G F 1 diff --git a/test/analysis/input/example/data1-pathlinker-params-6SWY7JS_pathway.txt b/test/analysis/input/example/data1-pathlinker-params-6SWY7JS_pathway.txt index ee289c96..52874b41 100644 --- a/test/analysis/input/example/data1-pathlinker-params-6SWY7JS_pathway.txt +++ b/test/analysis/input/example/data1-pathlinker-params-6SWY7JS_pathway.txt @@ -1,3 +1,3 @@ -A B 1 -B C 1 -A D 2 +A B 1 +B C 1 +A D 2 diff --git a/test/analysis/input/example/data1-pathlinker-params-VQL7BDZ_pathway.txt b/test/analysis/input/example/data1-pathlinker-params-VQL7BDZ_pathway.txt index ee289c96..52874b41 100644 --- a/test/analysis/input/example/data1-pathlinker-params-VQL7BDZ_pathway.txt +++ b/test/analysis/input/example/data1-pathlinker-params-VQL7BDZ_pathway.txt @@ -1,3 +1,3 @@ -A B 1 -B C 1 -A D 2 +A B 1 +B C 1 +A D 2 diff --git a/test/analysis/test_cytoscape.py b/test/analysis/test_cytoscape.py new file mode 100644 index 00000000..3ac5eca3 --- /dev/null +++ b/test/analysis/test_cytoscape.py @@ -0,0 +1,39 @@ +import shutil +from pathlib import Path + +import pytest + +from src.analysis.cytoscape import run_cytoscape + +INPUT_DIR = 'test/analysis/input/example/' +INPUT_PATHWAYS = [INPUT_DIR + 'data0-meo-params-GKEDDFZ_pathway.txt', + INPUT_DIR + 'data0-omicsintegrator1-params-RQCQ4YN_pathway.txt', + INPUT_DIR + 'data0-omicsintegrator1-params-WY4V42C_pathway.txt', + INPUT_DIR + 'data0-omicsintegrator2-params-IV3IPCJ_pathway.txt', + INPUT_DIR + 'data0-pathlinker-params-6SWY7JS_pathway.txt', + INPUT_DIR + 'data0-pathlinker-params-VQL7BDZ_pathway.txt'] +OUT_FILE = 'test/analysis/output/cytoscape.cys' + + +class TestCytoscape: + """ + Test creating a Cytoscape session file from a list of pathways + """ + def test_cytoscape(self): + out_path = Path(OUT_FILE) + out_path.unlink(missing_ok=True) + run_cytoscape(INPUT_PATHWAYS, OUT_FILE) + assert out_path.exists() + + # Only run Singularity test if the binary is available on the system + # spython is only available on Unix, but do not explicitly skip non-Unix platforms + # @pytest.mark.skipif(not shutil.which('singularity'), reason='Singularity not found on system') + # See https://github.com/Reed-CompBio/spras/pull/66#issuecomment-1730079719 for discussion of the test + # environment where this test passes. It fails in GitHub Actions. + # Open a GitHub issue if Cytoscape does not work on Singularity as expected for assistance debugging + @pytest.mark.xfail(reason='Requires Singularity and only works for certain Singularity configurations') + def test_cytoscape_singularity(self): + out_path = Path(OUT_FILE) + out_path.unlink(missing_ok=True) + run_cytoscape(INPUT_PATHWAYS, OUT_FILE, True) + assert out_path.exists()