diff --git a/CHANGELOG.md b/CHANGELOG.md index da26006..d425ddf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ * planned new feature: during import of long reads, (optionally) correct for short exon alignment issues. * separate new read import and classification of isoforms. +## [2.0.6] + +* fixed: `has_overlap`/`get_overlap` gave wrong results for reverse-strand features, where genomic coordinates are sometimes passed as `(end, start)` instead of `(start, end)`; this caused `add_domains_to_table` to silently miss overlapping domains on the reverse strand (#23) + ## [2.0.5] * fixed: gtf/gff3 import no longer reads the file twice for the progress bar (was pre-counting all lines); progress is now tracked by bytes read, roughly halving import time with `progress_bar=True` (#37) diff --git a/VERSION.txt b/VERSION.txt index e010258..157e54f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -2.0.5 +2.0.6 diff --git a/src/isotools/_utils.py b/src/isotools/_utils.py index 9c22acd..b0b00ce 100644 --- a/src/isotools/_utils.py +++ b/src/isotools/_utils.py @@ -276,14 +276,19 @@ def find_orfs(sequence, start_codons=None, stop_codons=None, ref_cds=None): def has_overlap(r1, r2): "check the overlap of two intervals" - # assuming start < end - return r1[1] > r2[0] and r2[1] > r1[0] + # coordinates on reverse-strand features are sometimes passed as (end, start); + # normalize rather than assume start < end. Only the first two elements + # are used -- r1/r2 may be e.g. an intervaltree.Interval with extra data + r1_lo, r1_hi = min(r1[0], r1[1]), max(r1[0], r1[1]) + r2_lo, r2_hi = min(r2[0], r2[1]), max(r2[0], r2[1]) + return r1_hi > r2_lo and r2_hi > r1_lo def get_overlap(r1, r2): "check the overlap of two intervals" - # assuming start < end - return max(0, min(r1[1], r2[1]) - max(r1[0], r2[0])) + r1_lo, r1_hi = min(r1[0], r1[1]), max(r1[0], r1[1]) + r2_lo, r2_hi = min(r2[0], r2[1]), max(r2[0], r2[1]) + return max(0, min(r1_hi, r2_hi) - max(r1_lo, r2_lo)) def get_intersects(tr1, tr2): diff --git a/tests/README.md b/tests/README.md index 16de03a..f7f1c41 100644 --- a/tests/README.md +++ b/tests/README.md @@ -27,3 +27,13 @@ Two different test styles exist in this directory: **When adding a test for a specific bug or feature, use style 1.** Reserve the pipeline chain for genuine end-to-end integration concerns. + +## File organization + +One test file per feature area, named after what it covers -- not +necessarily 1:1 with a source file, but close (`domain_test.py` for +`domains.py`, `splice_graph_test.py` for `splice_graph.py`, `orf_test.py` +for ORF-prediction logic in `gene.py`, `data_import_test.py` for import +functions in `_transcriptome_io.py`, `utils_test.py` for the general +primitives in `_utils.py`, etc.). Before creating a new file, check whether +an existing one already covers that area. diff --git a/tests/utils_test.py b/tests/utils_test.py new file mode 100644 index 0000000..773b76c --- /dev/null +++ b/tests/utils_test.py @@ -0,0 +1,18 @@ +from isotools._utils import has_overlap, get_overlap + + +def test_has_overlap_flipped_coordinates(): + # regression test for #23: coordinates on reverse-strand features are + # sometimes passed as (end, start) rather than (start, end) -- both + # orderings must give the same result + assert has_overlap((90, 118), (100, 200)) is True + assert has_overlap((118, 90), (100, 200)) is True # previously wrongly False + assert has_overlap((10, 20), (100, 200)) is False + assert has_overlap((20, 10), (100, 200)) is False + + +def test_get_overlap_flipped_coordinates(): + assert get_overlap((90, 118), (100, 200)) == 18 + assert get_overlap((118, 90), (100, 200)) == 18 # previously wrong result + assert get_overlap((10, 20), (100, 200)) == 0 + assert get_overlap((20, 10), (100, 200)) == 0