Skip to content
Merged

Dev #116

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ Easiest is to check [datasets](datasets) examples to see how the above files loo

## Changelog<a name="changelog"></a>

**v0.8.1**: released in July 2026

* added `bam_file` column support in `samples.tab` for per-sample BAM paths (subfolder layouts)
* new `get_bam_path(sample_id)` helper in `splicekit/core/annotation.py` — falls back to `{bam_path}/{sample_id}.bam` when no per-sample path is given
* `exons.py`, `genes.py`, `anchors.py`: replaced `os.listdir()` BAM discovery with `annotation.samples` list + `get_bam_path()` (enables subfolder BAMs, no dir scan needed)
* `junctions.py`, `jbrowse2.py`: same `get_bam_path()` adoption
* default `bam_column = "bam_file"` added to config

**v0.8**: released in October 2025

* removed platform config option (now snakemake submits jobs to the cluster)
Expand Down
1 change: 1 addition & 0 deletions splicekit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
splicekit.core.annotation.samples = set()
splicekit.core.annotation.comparisons = []
splicekit.core.annotation.genes = {}
splicekit.core.annotation.bam_path_map = {}
splicekit.core.annotation.make_comparisons() # load existing comparisons

def setup():
Expand Down
7 changes: 6 additions & 1 deletion splicekit/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,9 @@ def jbrowse2_config():
try:
clip
except:
clip = None
clip = None

try:
bam_column
except:
bam_column = "bam_file"
7 changes: 3 additions & 4 deletions splicekit/core/anchors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import sys
import splicekit.config as config
import splicekit.core.annotation as annotation
import gzip

def write_anchor_gtf():
Expand Down Expand Up @@ -69,7 +70,6 @@ def write_jobs_featureCounts(library_type='single-end', library_strand='NONE'):

for anchor_type in ["donor", "acceptor"]:
gtf_fname = f"reference/{anchor_type}_anchors.gtf.gz"
bam_dir = f"{config.bam_path}" # files inside end with <sample_id>.bam
out_dir = f'data/sample_{anchor_type}_anchors_data'
jobs_dir = f'jobs/count_{anchor_type}_anchors'
logs_dir = f'logs/count_{anchor_type}_anchors'
Expand Down Expand Up @@ -131,14 +131,13 @@ def write_jobs_featureCounts(library_type='single-end', library_strand='NONE'):
gzip -f {out_fname}
"""

bam_files = [fi for fi in os.listdir(bam_dir) if fi.endswith('.bam')]
sample_ids = [fi.replace('.bam', '') for fi in bam_files]
sample_ids = annotation.samples
header_line = '\t'.join(['anchor_id', 'count'])
fsh = open(f"{jobs_dir}/process.sh", "wt")
for sample in sample_ids:
out_fname = f"{out_dir}/sample_{sample}.tab"
job_fname = f'{jobs_dir}/{anchor_type}_anchors_{sample}.job'
sam_fname = f"{bam_dir}/{sample}.bam"
sam_fname = annotation.get_bam_path(sample)
# cluster job
job_out = job_anchors.format(container=config.container, library_type_insert=library_type_insert, library_strand_insert=library_strand_insert, anchor_type=anchor_type, gtf_fname=gtf_fname, sample_id=sample, sam_fname=sam_fname, out_fname=out_fname, logs_dir=logs_dir, header_line=header_line)
job_file = open(job_fname, "w")
Expand Down
19 changes: 17 additions & 2 deletions splicekit/core/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,25 @@ def run(self):
self.returncode = self.process.returncode
return output.decode("utf-8"), error.decode("utf-8")

def get_bam_path(sample_id):
"""Return the BAM file path for a sample.

If samples.tab contained a bam_file column (config.bam_column), returns
that per-sample path, allowing BAM files in arbitrary sub-folders.
Otherwise falls back to the flat {bam_path}/{sample_id}.bam convention.
"""
if sample_id in annotation.bam_path_map:
return annotation.bam_path_map[sample_id]
return os.path.join(config.bam_path, f"{sample_id}.bam")

def make_comparisons():
if not os.path.exists("samples.tab"):
return
annotation.comparisons = []
annotation.treatments = {}
annotation.bam_path_map = {}
samples = set()
bam_column = getattr(config, "bam_column", "bam_file")
f = open("samples.tab")
r = f.readline()
while r.startswith("#"):
Expand All @@ -80,6 +93,8 @@ def make_comparisons():
treatment_id = data[config.treatment_column]
separates.add(data.get(config.separate_column, ""))
annotation.treatments.setdefault(treatment_id, []).append((sample_id, treatment_id, data.get(config.separate_column, ""), data.get(config.group_column, "")))
if bam_column in header and data.get(bam_column, "").strip():
annotation.bam_path_map[sample_id] = data[bam_column].strip()
r = f.readline()
f.close()
for treatment, data in annotation.treatments.items(): # sort by sample ID
Expand Down Expand Up @@ -173,7 +188,7 @@ def write_comparisons():
for rtype, rfile in [("control", control_ids), ("test", test_ids)]:
bams = []
for sample_id in rfile:
bam_fname = os.path.abspath(os.path.join(f"{splicekit.config.bam_path}", f"{sample_id}.bam"))
bam_fname = os.path.abspath(get_bam_path(sample_id))
bams.append(bam_fname)
f_rmats = open(f"results/rmats/{comparison_name}_{rtype}.tab", "wt")
f_rmats.write(",".join(bams))
Expand Down Expand Up @@ -454,7 +469,7 @@ def bam_count():
while r:
r = r.replace("\r", "").replace("\n", "").split("\t")
data = dict(zip(header, r))
bam_fname = os.path.join(config.bam_path, data[config.sample_column]+".bam")
bam_fname = get_bam_path(data[config.sample_column])
output = subprocess.check_output(f"samtools view -@ 4 -c {bam_fname}", shell=True)
count = int(output.decode().split("\n")[0])
print(f"splicekit | bam read counts: {data[splicekit.config.sample_column]}, bam file {bam_fname}, counts = {count}")
Expand Down
7 changes: 3 additions & 4 deletions splicekit/core/exons.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys
import splicekit.config as config
import splicekit.core as core
import splicekit.core.annotation as annotation
import gzip

def write_exons_gtf():
Expand Down Expand Up @@ -61,7 +62,6 @@ def write_jobs_featureCounts(library_type='single-end', library_strand='NONE'):
library_strand_insert = {"FIRST_READ_TRANSCRIPTION_STRAND":1, "SINGLE_STRAND":1, "SINGLE_REVERSE":1, "SECOND_READ_TRANSCRIPTION_STRAND":2, "NONE":0}[library_strand]

gtf_fname = f"reference/exons.gtf.gz"
bam_dir = f"{config.bam_path}" # files inside end with <sample_id>.bam
out_dir = f'data/sample_exons_data'
jobs_dir = f'jobs/count_exons'
logs_dir = f'logs/count_exons'
Expand Down Expand Up @@ -124,14 +124,13 @@ def write_jobs_featureCounts(library_type='single-end', library_strand='NONE'):
gzip -f {out_fname}
"""

bam_files = [fi for fi in os.listdir(bam_dir) if fi.endswith('.bam')]
sample_ids = [fi.replace('.bam', '') for fi in bam_files]
sample_ids = annotation.samples
header_line = '\t'.join(['exon_id', 'count'])
fsh = open(f"{jobs_dir}/process.sh", "wt")
for sample in sample_ids:
out_fname = f"{out_dir}/sample_{sample}.tab"
job_fname = f'{jobs_dir}/exons_{sample}.job'
sam_fname = f"{bam_dir}/{sample}.bam"
sam_fname = annotation.get_bam_path(sample)
# cluster job
job_out = job_exons.format(container=config.container, library_type_insert=library_type_insert, library_strand_insert=library_strand_insert, gtf_fname=gtf_fname, sample_id=sample, sam_fname=sam_fname, out_fname=out_fname, logs_dir=logs_dir, header_line=header_line)
job_file = open(job_fname, "w")
Expand Down
7 changes: 3 additions & 4 deletions splicekit/core/genes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import sys
import splicekit.core as core
import splicekit.config as config
import splicekit.core.annotation as annotation
import gzip
import re

Expand Down Expand Up @@ -75,7 +76,6 @@ def write_jobs_featureCounts(library_type='single-end', library_strand='NONE'):
library_strand_insert = {"FIRST_READ_TRANSCRIPTION_STRAND":1, "SINGLE_STRAND":1, "SINGLE_REVERSE":1, "SECOND_READ_TRANSCRIPTION_STRAND":2, "NONE":0}[library_strand]

gtf_fname = f"reference/genes.gtf.gz"
bam_dir = f"{config.bam_path}" # files inside end with <sample_id>.bam
out_dir = f"data/sample_genes_data"
jobs_dir = f"jobs/count_genes"
logs_dir = f"logs/count_genes"
Expand Down Expand Up @@ -139,14 +139,13 @@ def write_jobs_featureCounts(library_type='single-end', library_strand='NONE'):
gzip -f {out_fname}
"""

bam_files = [fi for fi in os.listdir(bam_dir) if fi.endswith('.bam')]
sample_ids = [fi.replace('.bam', '') for fi in bam_files]
sample_ids = annotation.samples
header_line = '\t'.join(['gene_id', 'count'])
fsh = open(f"{jobs_dir}/process.sh", "wt")
for sample in sample_ids:
out_fname = f"{out_dir}/sample_{sample}.tab"
job_fname = f'{jobs_dir}/genes_{sample}.job'
sam_fname = f"{bam_dir}/{sample}.bam"
sam_fname = annotation.get_bam_path(sample)
# cluster job
job_out = job_genes.format(container=config.container, library_type_insert=library_type_insert, library_strand_insert=library_strand_insert, gtf_fname=gtf_fname, sample_id=sample, sam_fname=sam_fname, out_fname=out_fname, logs_dir=logs_dir, header_line=header_line)
job_file = open(job_fname, "w")
Expand Down
10 changes: 8 additions & 2 deletions splicekit/core/jbrowse2.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import splicekit.config as config
import splicekit.core as core
import splicekit.core.annotation as annotation
import http.server
import socket
import socketserver
Expand Down Expand Up @@ -40,6 +41,7 @@ def setup():
try:
bam_files = [fi for fi in os.listdir(bam_dir) if fi.endswith('.bam')]
except:
bam_files = []
print(f"{module_desc} no bam files found in provided bam folder")
genome_fa = config.fasta_path
gff_fname = config.gff3_path
Expand Down Expand Up @@ -100,10 +102,14 @@ def write_sample_jobs(force_samples):
os.mkdir(dir)

# create jobs
# If samples.tab provides per-sample bam paths, use those; otherwise scan bam_dir.
if annotation.bam_path_map:
sample_bam_pairs = [(sid + ".bam", annotation.get_bam_path(sid)) for sid in annotation.samples]
else:
sample_bam_pairs = [(fi, bam_dir + '/' + fi) for fi in bam_files]
bamCoverage_binSize= 3
fsh = open(f"jobs/jobs_jbrowse/process.sh", "wt")
for sample in bam_files:
bam_fname = bam_dir +'/'+ sample
for sample, bam_fname in sample_bam_pairs:
cram_fname = dirs_to_check[2] + '/'+sample.replace('.bam', '.cram')
bigwig_fname = dirs_to_check[2] +'/'+ sample.replace('.bam', '.bw')
if (not (os.path.exists(cram_fname) and os.path.exists(bigwig_fname))) or (force_samples == True):
Expand Down
2 changes: 1 addition & 1 deletion splicekit/core/junctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def make_jobs():

for sample_id in splicekit.core.annotation.samples:
core_path = os.path.dirname(splicekit.core.__file__)
bam_fname = f"{splicekit.config.bam_path}/{sample_id}.bam"
bam_fname = splicekit.core.annotation.get_bam_path(sample_id)
f = open("jobs/count_junctions/sample_{sample_id}.job".format(sample_id=sample_id), "wt")
f.write(job_junctions.format(sample_id=sample_id, core_path=core_path, bam_fname=bam_fname, job_name="count_junctions_{sample_id}".format(sample_id=sample_id)))
f.close()
Expand Down
2 changes: 1 addition & 1 deletion splicekit/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.8
0.8.1
Loading