Skip to content

Commit

Permalink
Merge pull request #22 from EBI-Metagenomics/create_release
Browse files Browse the repository at this point in the history
Create release action
  • Loading branch information
KateSakharova authored Jan 17, 2025
2 parents caed204 + dd1a0d1 commit 13cd283
Show file tree
Hide file tree
Showing 11 changed files with 349 additions and 38 deletions.
59 changes: 59 additions & 0 deletions .github/workflows/create_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Create Release with Images

on:
workflow_dispatch: # Allows manual triggering
push:
tags:
- "v*" # Trigger on version tags

jobs:
create-release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: 'pip' # caching pip dependencies

- name: Install dependencies
run: |
pip install .
- name: Install graphviz
run: sudo apt-get install -y graphviz

- name: Generate Images Folder
run: |
cat kegg_pathways_completeness/pathways_data/all_pathways.txt | tr ':' '\t' | cut -f1 > list_modules.txt
python -m kegg_pathways_completeness.bin.plot_modules_graphs --modules-file list_modules.txt --graphs kegg_pathways_completeness/pathways_data/graphs.pkl --definitions kegg_pathways_completeness/pathways_data/all_pathways.txt -o plots --use-pydot
rm list_modules.txt
- name: Compress Folder into GZIP
run: |
tar -czf plots.tar.gz ./plots
- name: Create GitHub Release
id: create_release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}
release_name: "Release ${{ github.ref_name }}"
body: "Automatically generated release ${{ github.ref_name }} with images."
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload Archive to Release
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./plots.tar.gz
asset_name: plots.tar.gz
asset_content_type: application/gzip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion kegg_pathways_completeness/bin/give_completeness.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ def process(self):
modules_definitions=self.modules_definitions,
outdir=self.name_output_pathways_plots
)
plot_completeness_generator.generate_plot_for_completeness()
plot_completeness_generator.generate_plot()
logger.info('...Done. Results are in pathways_plots folder')

# generate summary per-contig
Expand Down
143 changes: 107 additions & 36 deletions kegg_pathways_completeness/bin/plot_modules_graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import logging
import os
import graphviz
import pydot
import csv

from .utils import parse_modules_list_input, parse_graphs_input

Expand All @@ -43,6 +45,8 @@ def parse_args(argv):
default="pathways_data/all_pathways.txt")
parser.add_argument("-o", "--outdir", dest="outdir", help="Path to output directory", required=False,
default="pathways_plots")
parser.add_argument("--use-pydot", dest="use_pydot", help="Use pydot instead of graphviz", required=False,
action='store_true')
return parser.parse_args(argv)


Expand All @@ -54,6 +58,7 @@ def __init__(
modules_definitions: dict,
outdir: str,
modules_list: list = [],
use_pydot: bool = False
):
"""
Class generates a plots with colored edges depending on presence/absence.
Expand All @@ -67,13 +72,56 @@ def __init__(
self.modules_completeness = modules_completeness
self.graphs = graphs
self.modules_definitions = modules_definitions
self.outdir = outdir
self.modules_list = modules_list
# flags
self.use_pydot = use_pydot
# output directories
self.outdir = outdir
if not os.path.exists(self.outdir):
os.mkdir(self.outdir)
self.outdir_dot = os.path.join(self.outdir, 'dot')
if not os.path.exists(self.outdir_dot):
os.mkdir(self.outdir_dot)
self.outdir_png = os.path.join(self.outdir, 'png')
if not os.path.exists(self.outdir_png):
os.mkdir(self.outdir_png)

def format_weight(self, weight):
if weight in (0, 1):
return str(weight)
return f"1/{int(1 / weight)}"

def create_graph_dot(self, name, presented, graph, pathways_schema):
# Create a pydot graph
dot = pydot.Dot(name=name, graph_type='digraph', comment=pathways_schema)
edges = graph[0].edges

for edge, count in zip(edges, range(len(edges))):
from_node = edge[0]
to_node = edge[1]
number = edge[2]
# Add nodes to the graph
dot.add_node(pydot.Node(str(from_node)))
dot.add_node(pydot.Node(str(to_node)))
# Extract edge attributes
label = edges._adjdict[from_node][to_node][number]['label']
weight = edges._adjdict[from_node][to_node][number]['weight']
# Set edge color
color = 'red' if label in presented else 'black'
# Add edge to the graph
dot.add_edge(
pydot.Edge(
str(from_node),
str(to_node),
label=f"{label} \n [{self.format_weight(weight)}]",
color=color
)
)
return dot

def create_graph(self, name, presented, graph, pathways_schema):
dot = graphviz.Digraph(name, comment=pathways_schema)
edges = graph[0].edges
max_weight = 0
for edge, count in zip(edges, range(len(edges))):
from_node = edge[0]
to_node = edge[1]
Expand All @@ -83,39 +131,61 @@ def create_graph(self, name, presented, graph, pathways_schema):

label = edges._adjdict[from_node][to_node][number]['label']
weight = edges._adjdict[from_node][to_node][number]['weight']
if weight > 0:
if 1 / weight > max_weight:
max_weight = int(1 / weight)
if weight == 1 or weight == 0:
weight_str = str(weight)
else:
weight_str = '1/' + str(int(1 / weight))

color = 'red' if label in presented else 'black'
dot.edge(str(from_node), str(to_node), label=label + ' \n [' + weight_str + ']', color=color)
dot.edge(str(from_node), str(to_node), label=label + ' \n [' + self.format_weight(weight) + ']', color=color)
return dot

def generate_graph_using_pydot(self, name, presented_ko, graph, pathways_schema):
logging.info('Using pydot')
dot = self.create_graph_dot(name, presented=presented_ko, graph=graph,
pathways_schema=pathways_schema)
# create .dot file
with open(os.path.join(self.outdir_dot, f"{name}.dot"), "w") as f:
f.write(dot.to_string())
# create .png file
dot.write_png(os.path.join(self.outdir_png, f'{name}.png'))

def generate_graph_using_graphviz(self, name, presented_ko, graph, pathways_schema):
logging.info('Using graphviz')
dot = self.create_graph(name, presented=presented_ko, graph=graph,
pathways_schema=pathways_schema)
dot.render(directory=self.outdir, filename=name, format='png')

def generate_plot_for_completeness(self):
logging.info('Using completeness file')
for name in self.modules_completeness:
if len(self.modules_list):
if name not in self.modules_list:
logging.debug(f'Skipping {name} because it is not in specified modules list')
continue
graph = self.graphs[name]
logging.info(f'Plotting {name}')
presented_ko = self.modules_completeness[name].split(',')
if self.use_pydot:
self.generate_graph_using_pydot(name=name, presented_ko=presented_ko, graph=graph,
pathways_schema=self.modules_definitions[name])
else:
self.generate_graph_using_graphviz(name=name, presented_ko=presented_ko, graph=graph,
pathways_schema=self.modules_definitions[name])

def generate_plot_without_completeness(self):
logging.info("Plotting modules from specified list without completeness information")
for name in self.modules_list:
graph = self.graphs[name]
logging.info(f'Plotting {name}')
if self.use_pydot:
self.generate_graph_using_pydot(name=name, presented_ko=[], graph=graph,
pathways_schema=self.modules_definitions[name])
else:
self.generate_graph_using_graphviz(name=name, presented_ko=[], graph=graph,
pathways_schema=self.modules_definitions[name])

def generate_plot(self):
if self.modules_completeness:
logging.info('Using completeness file')
for name in self.modules_completeness:
if len(self.modules_list):
if name not in self.modules_list:
logging.debug(f'Skipping {name} because it is not in specified modules list')
continue
graph = self.graphs[name]
logging.info(f'Plotting {name}')
presented_ko = self.modules_completeness[name].split(',')
dot = self.create_graph(name, presented=presented_ko, graph=graph,
pathways_schema=self.modules_definitions[name])
dot.render(directory=self.outdir, filename=name, format='png')
self.generate_plot_for_completeness()
else:
logging.info("Plotting modules from specified list without completeness information")
for name in self.modules_list:
graph = self.graphs[name]
logging.info(f'Plotting {name}')
dot = self.create_graph(name, presented=[], graph=graph,
pathways_schema=self.modules_definitions[name])
dot.render(directory=self.outdir, filename=name, format='png')
self.generate_plot_without_completeness()


def parse_completeness_input(filepath):
Expand All @@ -139,17 +209,17 @@ def parse_completeness_input(filepath):
return pathways


def parse_input_modules(input_modules_list=None, input_modules_file=None):
def parse_input_modules(input_modules_list=None, input_modules_file=None, list_separator='\n'):
if input_modules_list:
logging.info("Using specified list of modules")
return input_modules_list
elif input_modules_file:
logging.info(f"Using modules from {input_modules_file}")
modules_list = []
with open(input_modules_file, 'r') as file_in:
for line in file_in:
modules_list.append(line.strip())
# TODO add separator
with open(input_modules_file, 'r') as f:
reader = csv.reader(f, delimiter=list_separator)
for row in reader:
modules_list.extend(row)
return modules_list
else:
logging.info('No modules specified in input')
Expand All @@ -166,9 +236,10 @@ def main():
graphs=parse_graphs_input(args.graphs),
modules_definitions=parse_modules_list_input(args.pathways),
outdir=args.outdir,
modules_list=parse_input_modules(args.input_modules_list, args.input_modules_file),
modules_list=parse_input_modules(args.input_modules_list, args.input_modules_file, args.list_separator),
use_pydot=args.use_pydot
)
plot_completeness_generator.generate_plot_for_completeness()
plot_completeness_generator.generate_plot()


if __name__ == "__main__":
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ classifiers = [
dependencies = [
"biopython>=1.83",
"networkx>=3.3",
"graphviz>=0.20.3"
"graphviz>=0.20.3",
"pydot>=3.0.4"
]

[build-system]
Expand Down
106 changes: 106 additions & 0 deletions tests/outputs/plot_modules_graphs/test_pydot/dot/M00001.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
digraph G {
name=M00001;
comment="(K00844,K12407,K00845,K25026,K00886,K08074,K00918) (K01810,K06859,K13810,K15916) (K00850,K16370,K21071,K24182,K00918) (K01623,K01624,K11645,K16305,K16306) K01803 ((K00134,K00150) K00927,K11389) (K01834,K15633,K15634,K15635) (K01689,K27394) (K00873,K12406)";
0;
2;
0 -> 2 [label="K00844 \n [1/9]", color=black];
0;
2;
0 -> 2 [label="K12407 \n [1/9]", color=black];
0;
2;
0 -> 2 [label="K00845 \n [1/9]", color=black];
0;
2;
0 -> 2 [label="K25026 \n [1/9]", color=black];
0;
2;
0 -> 2 [label="K00886 \n [1/9]", color=black];
0;
2;
0 -> 2 [label="K08074 \n [1/9]", color=black];
0;
2;
0 -> 2 [label="K00918 \n [1/9]", color=black];
2;
3;
2 -> 3 [label="K01810 \n [1/9]", color=black];
2;
3;
2 -> 3 [label="K06859 \n [1/9]", color=black];
2;
3;
2 -> 3 [label="K13810 \n [1/9]", color=black];
2;
3;
2 -> 3 [label="K15916 \n [1/9]", color=black];
3;
4;
3 -> 4 [label="K00850 \n [1/9]", color=black];
3;
4;
3 -> 4 [label="K16370 \n [1/9]", color=black];
3;
4;
3 -> 4 [label="K21071 \n [1/9]", color=black];
3;
4;
3 -> 4 [label="K24182 \n [1/9]", color=black];
3;
4;
3 -> 4 [label="K00918 \n [1/9]", color=black];
4;
5;
4 -> 5 [label="K01623 \n [1/9]", color=black];
4;
5;
4 -> 5 [label="K01624 \n [1/9]", color=black];
4;
5;
4 -> 5 [label="K11645 \n [1/9]", color=black];
4;
5;
4 -> 5 [label="K16305 \n [1/9]", color=black];
4;
5;
4 -> 5 [label="K16306 \n [1/9]", color=black];
5;
6;
5 -> 6 [label="K01803 \n [1/9]", color=black];
6;
8;
6 -> 8 [label="K00134 \n [1/18]", color=black];
6;
8;
6 -> 8 [label="K00150 \n [1/18]", color=black];
6;
7;
6 -> 7 [label="K11389 \n [1/9]", color=black];
7;
9;
7 -> 9 [label="K01834 \n [1/9]", color=black];
7;
9;
7 -> 9 [label="K15633 \n [1/9]", color=black];
7;
9;
7 -> 9 [label="K15634 \n [1/9]", color=black];
7;
9;
7 -> 9 [label="K15635 \n [1/9]", color=black];
8;
7;
8 -> 7 [label="K00927 \n [1/18]", color=black];
9;
10;
9 -> 10 [label="K01689 \n [1/9]", color=black];
9;
10;
9 -> 10 [label="K27394 \n [1/9]", color=black];
10;
1;
10 -> 1 [label="K00873 \n [1/9]", color=red];
10;
1;
10 -> 1 [label="K12406 \n [1/9]", color=black];
}
Loading

0 comments on commit 13cd283

Please sign in to comment.