Skip to content

Commit 659c1b5

Browse files
committed
rough drafting to reach PoC
1 parent 9eeaed3 commit 659c1b5

8 files changed

Lines changed: 253 additions & 408 deletions

File tree

bin/markdown_to_html.py

Lines changed: 87 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,101 @@
1-
#!/usr/bin/env python
2-
from __future__ import print_function
1+
#!/usr/bin/env python3
2+
33
import argparse
4-
import markdown
5-
import os
64
import sys
5+
import markdown
76

8-
def convert_markdown(in_fn):
9-
input_md = open(in_fn, mode="r", encoding="utf-8").read()
7+
def convert_markdown(md_file):
8+
"""Convert markdown to HTML with basic extensions only."""
9+
with open(md_file, 'r') as f:
10+
md_content = f.read()
11+
12+
# Use only basic extensions that come with markdown
1013
html = markdown.markdown(
11-
"[TOC]\n" + input_md,
12-
extensions = [
13-
'pymdownx.extra',
14-
'pymdownx.b64',
15-
'pymdownx.highlight',
16-
'pymdownx.emoji',
17-
'pymdownx.tilde',
18-
'toc'
19-
],
20-
extension_configs = {
21-
'pymdownx.b64': {
22-
'base_path': os.path.dirname(in_fn)
23-
},
24-
'pymdownx.highlight': {
25-
'noclasses': True
26-
},
27-
'toc': {
28-
'title': 'Table of Contents'
29-
}
30-
}
14+
md_content,
15+
extensions=[
16+
'markdown.extensions.tables',
17+
'markdown.extensions.fenced_code',
18+
'markdown.extensions.codehilite',
19+
'markdown.extensions.toc'
20+
]
3121
)
3222
return html
3323

34-
def wrap_html(contents):
35-
header = """<!DOCTYPE html><html>
36-
<head>
37-
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
38-
<style>
39-
body {
40-
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
41-
padding: 3em;
42-
margin-right: 350px;
43-
max-width: 100%;
44-
}
45-
.toc {
46-
position: fixed;
47-
right: 20px;
48-
width: 300px;
49-
padding-top: 20px;
50-
overflow: scroll;
51-
height: calc(100% - 3em - 20px);
52-
}
53-
.toctitle {
54-
font-size: 1.8em;
55-
font-weight: bold;
56-
}
57-
.toc > ul {
58-
padding: 0;
59-
margin: 1rem 0;
60-
list-style-type: none;
61-
}
62-
.toc > ul ul { padding-left: 20px; }
63-
.toc > ul > li > a { display: none; }
64-
img { max-width: 800px; }
65-
pre {
66-
padding: 0.6em 1em;
67-
}
68-
h2 {
24+
def wrap_html(body_html, title="Analysis Results"):
25+
"""Wrap the converted markdown in a complete HTML document."""
26+
return f"""<!DOCTYPE html>
27+
<html lang="en">
28+
<head>
29+
<meta charset="UTF-8">
30+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
31+
<title>{title}</title>
32+
<style>
33+
body {{
34+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
35+
line-height: 1.6;
36+
color: #333;
37+
max-width: 1200px;
38+
margin: 0 auto;
39+
padding: 20px;
40+
}}
41+
h1, h2, h3 {{ color: #2c3e50; }}
42+
code {{
43+
background-color: #f4f4f4;
44+
padding: 2px 4px;
45+
border-radius: 3px;
46+
font-family: 'Courier New', monospace;
47+
}}
48+
pre {{
49+
background-color: #f4f4f4;
50+
padding: 10px;
51+
border-radius: 5px;
52+
overflow-x: auto;
53+
}}
54+
table {{
55+
border-collapse: collapse;
56+
width: 100%;
57+
margin: 20px 0;
58+
}}
59+
th, td {{
60+
border: 1px solid #ddd;
61+
padding: 8px;
62+
text-align: left;
63+
}}
64+
th {{ background-color: #f2f2f2; }}
65+
.toc {{ background-color: #f9f9f9; padding: 15px; border-radius: 5px; }}
66+
</style>
67+
</head>
68+
<body>
69+
{body_html}
70+
</body>
71+
</html>"""
72+
73+
def main():
74+
parser = argparse.ArgumentParser(description='Convert Markdown to HTML')
75+
parser.add_argument('mdfile', type=argparse.FileType('r'), help='Input markdown file')
76+
parser.add_argument('-o', '--output', required=True, help='Output HTML file')
77+
parser.add_argument('--title', default='Analysis Results', help='HTML page title')
6978

70-
}
71-
</style>
72-
</head>
73-
<body>
74-
<div class="container">
75-
"""
76-
footer = """
77-
</div>
78-
</body>
79-
</html>
80-
"""
81-
return header + contents + footer
79+
args = parser.parse_args()
8280

81+
try:
82+
converted_md = convert_markdown(args.mdfile.name)
83+
full_html = wrap_html(converted_md, args.title)
8384

84-
def parse_args(args=None):
85-
parser = argparse.ArgumentParser()
86-
parser.add_argument('mdfile', type=argparse.FileType('r'), nargs='?',
87-
help='File to convert. Defaults to stdin.')
88-
parser.add_argument('-o', '--out', type=argparse.FileType('w'),
89-
default=sys.stdout,
90-
help='Output file name. Defaults to stdout.')
91-
return parser.parse_args(args)
85+
with open(args.output, 'w') as f:
86+
f.write(full_html)
9287

93-
def main(args=None):
94-
args = parse_args(args)
95-
converted_md = convert_markdown(args.mdfile.name)
96-
html = wrap_html(converted_md)
97-
args.out.write(html)
88+
print(f"Successfully converted {args.mdfile.name} to {args.output}")
89+
90+
except Exception as e:
91+
print(f"Error converting markdown: {e}", file=sys.stderr)
92+
return 1
93+
94+
finally:
95+
args.mdfile.close()
96+
97+
return 0
9898

9999
if __name__ == '__main__':
100100
sys.exit(main())
101+

main.nf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ workflow.onComplete {
396396
def email_html = html_template.toString()
397397

398398
// Render the sendmail template
399-
def smail_fields = [ email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, baseDir: "$baseDir", mqcFile: mqc_report, mqcMaxSize: params.max_multiqc_email_size.toBytes() ]
399+
def smail_fields = [ email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, baseDir: "$baseDir", mqcFile: mqc_report, mqcMaxSize: params.max_multiqc_email_size ]
400400
def sf = new File("$baseDir/assets/sendmail_template.txt")
401401
def sendmail_template = engine.createTemplate(sf).make(smail_fields)
402402
def sendmail_html = sendmail_template.toString()

modules/local/environment.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: resolvi
2+
channels:
3+
- conda-forge
4+
- bioconda
5+
- pytorch
6+
dependencies:
7+
- python=3.9
8+
- scanpy=1.9.6
9+
- scvi-tools=1.0.4
10+
- decoupler=1.4.0
11+
- matplotlib=3.7.1
12+
- seaborn=0.12.2
13+
- pandas=2.0.3
14+
- numpy=1.24.3
15+
- scipy=1.11.1
16+
- scikit-learn=1.3.0
17+
- anndata=0.9.2
18+
- h5py=3.9.0
19+
- pytorch=2.0.1
20+
- torchvision=0.15.2
21+
- torchaudio=2.0.2
22+
- lightning=2.0.0
23+
- jax=0.4.13
24+
- jaxlib=0.4.13
25+
- numba=0.57.1
26+
- networkx=3.1
27+
- statsmodels=0.14.0
28+
- adjusttext=0.7.3
29+
- plotly=5.15.0
30+
- markdown=3.4.4
31+
- pip=23.2.1
32+
- pip:
33+
- mudata>=0.2.3
34+
- omnipath>=1.0.6
35+

modules/local/resolvi_analyze.nf

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ process RESOLVI_ANALYZE {
44
label 'process_gpu'
55

66
conda "bioconda::scvi-tools bioconda::decoupler"
7-
container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ?
8-
'https://depot.galaxyproject.org/singularity/scvi-tools:1.0.4--pyhdfd78af_0' :
9-
'quay.io/biocontainers/scvi-tools:1.0.4--pyhdfd78af_0' }"
7+
container 'oras://community.wave.seqera.io/library/pip_decoupler_scanpy_scvi-tools:8124a7e473830fad'
108

119
input:
1210
tuple val(meta), path(model_dir), path(adata)

modules/local/resolvi_preprocess.nf

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,64 @@ process RESOLVI_PREPROCESS {
22
tag "$meta.id"
33
label 'process_medium'
44

5-
conda "bioconda::scanpy bioconda::spatialdata"
6-
container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ?
7-
'https://depot.galaxyproject.org/singularity/scanpy:1.9.3--pyhd8ed1ab_0' :
8-
'quay.io/biocontainers/scanpy:1.9.3--pyhd8ed1ab_0' }"
5+
container 'oras://community.wave.seqera.io/library/pip_decoupler_scanpy_scvi-tools:8124a7e473830fad'
96

107
input:
11-
tuple val(meta), path(zarr_store)
8+
tuple val(meta), path(adata)
129

1310
output:
14-
tuple val(meta), path("*.h5ad"), emit: adata
15-
path "versions.yml", emit: versions
11+
tuple val(meta), path("*_preprocessed.h5ad"), emit: adata
12+
path "versions.yml" , emit: versions
13+
14+
when:
15+
task.ext.when == null || task.ext.when
1616

1717
script:
18+
def args = task.ext.args ?: ''
19+
def prefix = task.ext.prefix ?: "${meta.id}"
20+
1821
"""
19-
#!/usr/bin/env python3
20-
21-
import spatialdata as sd
22-
import scanpy as sc
23-
import pandas as pd
24-
import numpy as np
25-
26-
# Load spatialdata zarr store
27-
sdata = sd.read_zarr("${zarr_store}")
28-
29-
# Convert to AnnData (adjust based on your sopa output structure)
30-
adata = sdata.tables['table'] # Adjust key as needed
31-
32-
# Setup spatial coordinates
33-
if 'X_spatial' not in adata.obsm:
34-
# Extract coordinates from spatialdata
35-
coords = sdata.points['transcripts'][['x', 'y']].values # Adjust as needed
36-
adata.obsm['X_spatial'] = coords
37-
38-
# Ensure required columns exist
39-
if 'annotation' not in adata.obs and 'cell_type' in adata.obs:
40-
adata.obs['annotation'] = adata.obs['cell_type']
41-
42-
# Setup counts layer
43-
if 'counts' not in adata.layers:
44-
if 'raw_counts' in adata.layers:
45-
adata.layers['counts'] = adata.layers['raw_counts']
46-
else:
47-
adata.layers['counts'] = adata.X.copy()
48-
49-
# Save preprocessed data
50-
adata.write_h5ad("${meta.id}_preprocessed.h5ad")
51-
52-
# Write versions
53-
with open("versions.yml", "w") as f:
54-
f.write('"${task.process}":\\n')
55-
f.write(f' scanpy: {sc.__version__}\\n')
56-
f.write(f' spatialdata: {sd.__version__}\\n')
22+
python3 << 'EOF'
23+
import scanpy as sc
24+
import pandas as pd
25+
import numpy as np
26+
import scvi
27+
import decoupler as dc
28+
29+
# Load data
30+
adata = sc.read_h5ad("${adata}")
31+
32+
# Basic preprocessing
33+
sc.pp.calculate_qc_metrics(adata, percent_top=None, log1p=False, inplace=True)
34+
35+
# Save preprocessed data
36+
adata.write("${prefix}_preprocessed.h5ad")
37+
38+
# Write versions
39+
versions = {
40+
"scanpy": sc.__version__,
41+
"scvi-tools": scvi.__version__,
42+
"decoupler": dc.__version__
43+
}
44+
45+
with open("versions.yml", "w") as f:
46+
f.write('"${task.process}":\\n')
47+
for tool, version in versions.items():
48+
f.write(f' {tool}: {version}\\n')
49+
EOF
50+
"""
51+
52+
stub:
53+
def prefix = task.ext.prefix ?: "${meta.id}"
54+
"""
55+
touch ${prefix}_preprocessed.h5ad
56+
57+
cat <<-END_VERSIONS > versions.yml
58+
"${task.process}":
59+
scanpy: 1.9.6
60+
scvi-tools: 1.0.4
61+
decoupler: 1.4.0
62+
END_VERSIONS
5763
"""
5864
}
65+

modules/local/resolvi_train.nf

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ process RESOLVI_TRAIN {
44
label 'process_gpu'
55

66
conda "bioconda::scvi-tools"
7-
container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ?
8-
'https://depot.galaxyproject.org/singularity/scvi-tools:1.0.4--pyhdfd78af_0' :
9-
'quay.io/biocontainers/scvi-tools:1.0.4--pyhdfd78af_0' }"
7+
container 'oras://community.wave.seqera.io/library/pip_decoupler_scanpy_scvi-tools:8124a7e473830fad'
108

119
input:
1210
tuple val(meta), path(adata)

0 commit comments

Comments
 (0)