From a9a7eef85145c04ee1a7638631c58847861825be Mon Sep 17 00:00:00 2001 From: Barry digby Date: Wed, 2 Sep 2026 15:07:56 +0100 Subject: [PATCH] Bump version to 1.0.1: restore stranded hg19/hg38 coords in miRNA/RBP outputs --- assets/manuscript.svg | 1035 +++++++++++++++++++++++++++++++++++ docs/conf.py | 4 +- docs/outputs.rst | 26 +- pyproject.toml | 2 +- utils/mirna/mirna_driver.py | 24 +- utils/rbp/rbp_driver.py | 23 +- uv.lock | 26 +- 7 files changed, 1096 insertions(+), 44 deletions(-) create mode 100644 assets/manuscript.svg diff --git a/assets/manuscript.svg b/assets/manuscript.svg new file mode 100644 index 0000000..8aa5141 --- /dev/null +++ b/assets/manuscript.svg @@ -0,0 +1,1035 @@ + + + +chr1:1248-2482|-chr2:9238-9932|-chr1:43321-53351chr4:5839-6928|+chrX:3623-5589chrM:9829-9921|-{ "global_parameters": { "output_dir": "results/" }, "samples": { "SRR5133906_CIRI": { "file_path": "ciri.txt", "reference": "hg19" }...Input circRNAsConfigLookup Table MatchingExecution ReportDatabase IDhg19hg38- Software versioning- Database provenance- Configuration parameters- Sample lineage- Per-database mapping statisticsAnnotation- Returns per-database *.txt files- Database identifier, hg19, hg38 columns for cross database comparisons.- Database specific metadataFASTA- Returns per-database FASTA files- circRNA ID FASTA header- Filenames denote reference genome from which sequence was derivedmiRNA- Per chromsome *.txt.gz files- TargetScan MSA start, MSA end, miRNA Site type- miRanda minumum free energy, binding site scoreRBP- Per chromsome *.txt.gz files- RBP symbol, peak counts within circRNA sequence window- Experimental information1234hsa-SKI_0001hsa_circ_001345CIRCHSA_PM1_9ASCRP3019820hsa_STL7_00156exo_circ_000123chr1:1002-2201|-chr2:9238-9932|-chr1:4332-5335|+chrX:3623-5589|-chr10:1323-3231|+chrM:9828-9921|-chr1:1256-2500|-chr2:9256-9940|-chr1:4350-5360|+chrx:3711-5798|-chr10:1423-3312|+chrM:9918-10212|- diff --git a/docs/conf.py b/docs/conf.py index 7523dde..4ca1a39 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,8 +10,8 @@ copyright = '2026, Barry Digby' author = 'Barry Digby' -release = '0.1' -version = '0.1.5' +release = '1.0' +version = '1.0.1' # -- General configuration diff --git a/docs/outputs.rst b/docs/outputs.rst index 0c6f8b8..704ad31 100644 --- a/docs/outputs.rst +++ b/docs/outputs.rst @@ -527,11 +527,11 @@ miRNA output are a combination of CircNET and CSCD - for this reason, not all Ta .. code-block:: text - circRNA miRNA MSA_start MSA_end Site_type Score Energy Algorithm - chr15:100048856-100054054|+ miR-1208 30.0 37.0 8mer-1a 144.0 -13.62 miRanda,TargetScan - chr15:100048856-100054054|+ miR-1237-5p/4488-5p 140.0 -20.01 miRanda - chr15:100048856-100054054|+ miR-1289 141.0 -22.95 miRanda - chr15:100048856-100054054|+ miR-141-5p 39.0 45.0 7mer-m8 178.0 -26.76 miRanda,TargetScan + hg19 hg38 miRNA MSA_start MSA_end Site_type Score Energy Algorithm + chr15:100048856-100054054|+ chr15:100048856-100054054|+ miR-1208 30.0 37.0 8mer-1a 144.0 -13.62 miRanda,TargetScan + chr15:100048856-100054054|+ chr15:100048856-100054054|+ miR-1237-5p/4488-5p 140.0 -20.01 miRanda + chr15:100048856-100054054|+ chr15:100048856-100054054|+ miR-1289 141.0 -22.95 miRanda + chr15:100048856-100054054|+ chr15:100048856-100054054|+ miR-141-5p 39.0 45.0 7mer-m8 178.0 -26.76 miRanda,TargetScan .. list-table:: :header-rows: 1 @@ -539,7 +539,9 @@ miRNA output are a combination of CircNET and CSCD - for this reason, not all Ta * - Column Name - Description - * - circRNA + * - hg19 + - The circRNA coordinates in the hg19 reference genome. + * - hg38 - The circRNA coordinates in the hg38 reference genome. * - miRNA - The miRNA ID. @@ -561,10 +563,10 @@ RBP .. code-block:: text - circRNA Chr Start End RBP_symbol Information RBP_numbers source - chr18:12493071-12546904|+ chr18 12503834 12546883 AGO2 SBDH105-59581 24 common - chr18:12493071-12546904|+ chr18 12498779 12546872 FBL SBDH120-145469 10 common - chr18:12493071-12546904|+ chr18 12493071 12546757 U2AF2 SBDH19-189369 100 common + hg19 hg38 Chr Start End RBP_symbol Information RBP_numbers source + chr18:12493071-12546904|+ chr18:12493071-12546904|+ chr18 12503834 12546883 AGO2 SBDH105-59581 24 common + chr18:12493071-12546904|+ chr18:12493071-12546904|+ chr18 12498779 12546872 FBL SBDH120-145469 10 common + chr18:12493071-12546904|+ chr18:12493071-12546904|+ chr18 12493071 12546757 U2AF2 SBDH19-189369 100 common .. list-table:: :header-rows: 1 @@ -572,7 +574,9 @@ RBP * - Column Name - Description - * - circRNA + * - hg19 + - The circRNA coordinates in the hg19 reference genome. + * - hg38 - The circRNA coordinates in the hg38 reference genome. * - Chr - Chromosome of the RBP binding site. diff --git a/pyproject.toml b/pyproject.toml index 81c8ad9..71a952e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pycircdb" -version = "1.0.0" +version = "1.0.1" description = "pycircdb: integrated circRNA database annotation for computational workflows." readme = "README.md" authors = [{ name = "Barry Digby", email = "b.digby237@gmail.com" }] diff --git a/utils/mirna/mirna_driver.py b/utils/mirna/mirna_driver.py index a81d9b8..7ca895e 100644 --- a/utils/mirna/mirna_driver.py +++ b/utils/mirna/mirna_driver.py @@ -30,16 +30,18 @@ def broadcast_mirna( for sample_name, lookup_hits in lookup_dict.items(): # Combine all hg38 IDs for this sample across all databases hg38_series = [] - # Map strand-stripped hg38 key -> full (stranded) hg38 coordinate, - # so strand can be restored on the output after the strand-less match. + # Map strand-stripped hg38 key -> stranded hg38/hg19 coordinates, + # so both can be restored on the output after the strand-less match. strand_frames = [] for db_name, pl_hits in lookup_hits.items(): if pl_hits.is_empty() or "hg38" not in pl_hits.columns: continue hg38_series.append(pl_hits["hg38"].str.split("|").list.first()) + hg19_col = pl.col("hg19") if "hg19" in pl_hits.columns else pl.lit(None, dtype=pl.Utf8).alias("hg19") strand_frames.append(pl_hits.select( pl.col("hg38").str.split("|").list.first().alias("_hg38_key"), - pl.col("hg38").alias("circRNA"), + pl.col("hg38"), + hg19_col, )) if not hg38_series: @@ -50,7 +52,7 @@ def broadcast_mirna( # Prefer stranded coordinates when de-duplicating the strand-less key. strand_map = ( pl.concat(strand_frames) - .sort("circRNA") + .sort("hg38") .unique(subset="_hg38_key", keep="last") ) @@ -96,16 +98,20 @@ def mirna_hits(broadcast_mirna: miRNABroadcast) -> None: if not df.is_empty(): # The miRNA table's circRNA column is the strand-stripped hg38 key. - # Join back to restore the strand onto the circRNA coordinate. + # Join back to restore stranded hg38/hg19 coordinates onto the output. if strand_map is not None and strand_map.height > 0: df = ( df.rename({"circRNA": "_hg38_key"}) .join(strand_map, on="_hg38_key", how="left") - # Fall back to the strand-less key if a coordinate is unmapped. - .with_columns(pl.col("circRNA").fill_null(pl.col("_hg38_key"))) + # Fall back to the strand-less key if hg38 is unmapped. + .with_columns(pl.col("hg38").fill_null(pl.col("_hg38_key"))) ) - ordered = ["circRNA"] + [c for c in df.columns if c not in ("circRNA", "_hg38_key")] - df = df.select(ordered) + else: + # No strand info available: fall back to the strand-less key for hg38. + df = df.rename({"circRNA": "hg38"}).with_columns(pl.lit(None, dtype=pl.Utf8).alias("hg19")) + + ordered = ["hg19", "hg38"] + [c for c in df.columns if c not in ("hg19", "hg38", "_hg38_key")] + df = df.select(ordered) p = Path(output_dir) if p.is_absolute(): diff --git a/utils/rbp/rbp_driver.py b/utils/rbp/rbp_driver.py index 49ee29c..cc4ed1c 100644 --- a/utils/rbp/rbp_driver.py +++ b/utils/rbp/rbp_driver.py @@ -29,15 +29,18 @@ def broadcast_rbp( for sample_name, lookup_hits in lookup_dict.items(): # Combine all hg38 IDs for this sample across all databases hg38_series = [] - # Same code as mirna_driver below + # Map strand-stripped hg38 key -> stranded hg38/hg19 coordinates, + # so both can be restored on the output after the strand-less match. strand_frames = [] for db_name, pl_hits in lookup_hits.items(): if pl_hits.is_empty() or "hg38" not in pl_hits.columns: continue hg38_series.append(pl_hits["hg38"].str.split("|").list.first()) + hg19_col = pl.col("hg19") if "hg19" in pl_hits.columns else pl.lit(None, dtype=pl.Utf8).alias("hg19") strand_frames.append(pl_hits.select( pl.col("hg38").str.split("|").list.first().alias("_hg38_key"), - pl.col("hg38").alias("circRNA"), + pl.col("hg38"), + hg19_col, )) if not hg38_series: @@ -48,7 +51,7 @@ def broadcast_rbp( # Prefer stranded coordinates when de-duplicating the strand-less key. strand_map = ( pl.concat(strand_frames) - .sort("circRNA") + .sort("hg38") .unique(subset="_hg38_key", keep="last") ) @@ -88,16 +91,20 @@ def rbp_hits(broadcast_rbp: RBPBroadcast) -> None: if not df.is_empty(): # The RBP table's circRNA column is the strand-stripped hg38 key. - # Join back to restore the strand onto the circRNA coordinate. + # Join back to restore stranded hg38/hg19 coordinates onto the output. if strand_map is not None and strand_map.height > 0: df = ( df.rename({"circRNA": "_hg38_key"}) .join(strand_map, on="_hg38_key", how="left") - # Fall back to the strand-less key if a coordinate is unmapped. - .with_columns(pl.col("circRNA").fill_null(pl.col("_hg38_key"))) + # Fall back to the strand-less key if hg38 is unmapped. + .with_columns(pl.col("hg38").fill_null(pl.col("_hg38_key"))) ) - ordered = ["circRNA"] + [c for c in df.columns if c not in ("circRNA", "_hg38_key")] - df = df.select(ordered) + else: + # No strand info available: fall back to the strand-less key for hg38. + df = df.rename({"circRNA": "hg38"}).with_columns(pl.lit(None, dtype=pl.Utf8).alias("hg19")) + + ordered = ["hg19", "hg38"] + [c for c in df.columns if c not in ("hg19", "hg38", "_hg38_key")] + df = df.select(ordered) p = Path(output_dir) if p.is_absolute(): diff --git a/uv.lock b/uv.lock index ef6be46..6eecdae 100644 --- a/uv.lock +++ b/uv.lock @@ -3,13 +3,13 @@ revision = 3 requires-python = ">=3.10.1" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version < '3.11' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", - "python_full_version < '3.11' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11' and sys_platform == 'win32'", + "python_full_version < '3.11' and sys_platform == 'emscripten'", "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] @@ -156,10 +156,10 @@ version = "1.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -535,10 +535,10 @@ version = "3.11.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -692,10 +692,10 @@ version = "2.4.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } @@ -854,10 +854,10 @@ version = "3.0.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -1040,7 +1040,7 @@ wheels = [ [[package]] name = "pycircdb" -version = "0.1.5" +version = "1.0.0" source = { editable = "." } dependencies = [ { name = "apache-hamilton" },