scanitd 0.9.2__tar.gz → 0.9.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. scanitd-0.9.3/.github/workflows/release.yml +88 -0
  2. {scanitd-0.9.2 → scanitd-0.9.3}/.gitignore +4 -1
  3. {scanitd-0.9.2 → scanitd-0.9.3}/PKG-INFO +29 -10
  4. {scanitd-0.9.2 → scanitd-0.9.3}/README.md +27 -7
  5. scanitd-0.9.3/conda/meta.yaml +53 -0
  6. scanitd-0.9.3/docs/source/changelog.md +81 -0
  7. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/conf.py +1 -1
  8. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/guide/usage.md +2 -1
  9. {scanitd-0.9.2 → scanitd-0.9.3}/pyproject.toml +13 -2
  10. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/__init__.py +1 -1
  11. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/cli/cli.py +16 -0
  12. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/inference/helper.py +10 -5
  13. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/inference/main.py +12 -1
  14. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/inference/sr_resuer.py +5 -4
  15. scanitd-0.9.3/src/scanitd/ssw/LICENSE-ssw-py.txt +58 -0
  16. scanitd-0.9.3/src/scanitd/ssw/Makefile +17 -0
  17. scanitd-0.9.3/src/scanitd/ssw/__init__.py +9 -0
  18. scanitd-0.9.3/src/scanitd/ssw/alignment.py +175 -0
  19. scanitd-0.9.3/src/scanitd/ssw/build_ssw.py +45 -0
  20. scanitd-0.9.3/src/scanitd/ssw/sse2neon.h +9074 -0
  21. scanitd-0.9.3/src/scanitd/ssw/ssw.c +995 -0
  22. scanitd-0.9.3/src/scanitd/ssw/ssw.h +194 -0
  23. scanitd-0.9.3/src/scanitd/ssw/str_util.c +138 -0
  24. scanitd-0.9.3/src/scanitd/ssw/str_util.h +51 -0
  25. {scanitd-0.9.2 → scanitd-0.9.3}/tests/test_helper.py +20 -4
  26. scanitd-0.9.3/tests/test_main.py +112 -0
  27. scanitd-0.9.3/tests/test_sr_resuer.py +135 -0
  28. scanitd-0.9.3/tests/test_ssw.py +117 -0
  29. scanitd-0.9.2/.github/workflows/release.yml +0 -50
  30. scanitd-0.9.2/docs/source/changelog.md +0 -43
  31. {scanitd-0.9.2 → scanitd-0.9.3}/.pre-commit-config.yaml +0 -0
  32. {scanitd-0.9.2 → scanitd-0.9.3}/.readthedocs.yaml +0 -0
  33. {scanitd-0.9.2 → scanitd-0.9.3}/CODE_OF_CONDUCT.md +0 -0
  34. {scanitd-0.9.2 → scanitd-0.9.3}/CONTRIBUTING.md +0 -0
  35. {scanitd-0.9.2 → scanitd-0.9.3}/LICENSE +0 -0
  36. {scanitd-0.9.2 → scanitd-0.9.3}/MANIFEST.in +0 -0
  37. {scanitd-0.9.2 → scanitd-0.9.3}/Makefile +0 -0
  38. {scanitd-0.9.2 → scanitd-0.9.3}/docs/Makefile +0 -0
  39. {scanitd-0.9.2 → scanitd-0.9.3}/docs/make.bat +0 -0
  40. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/api/base.rst +0 -0
  41. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/api/cli.rst +0 -0
  42. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/api/inference.rst +0 -0
  43. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/api/writer.rst +0 -0
  44. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/contributing.md +0 -0
  45. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/guide/installation.md +0 -0
  46. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/guide/output.md +0 -0
  47. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/guide/quickstart.md +0 -0
  48. {scanitd-0.9.2 → scanitd-0.9.3}/docs/source/index.rst +0 -0
  49. {scanitd-0.9.2 → scanitd-0.9.3}/requirements_dev.txt +0 -0
  50. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/base/__init__.py +0 -0
  51. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/base/basic.py +0 -0
  52. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/base/basic_read.py +0 -0
  53. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/base/cigar.py +0 -0
  54. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/cli/__init__.py +0 -0
  55. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/inference/__init__.py +0 -0
  56. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/mtype.py +0 -0
  57. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/py.typed +0 -0
  58. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/writer/__init__.py +0 -0
  59. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/writer/vcf_writer.py +0 -0
  60. {scanitd-0.9.2 → scanitd-0.9.3}/src/scanitd/writer/writer.py +0 -0
  61. {scanitd-0.9.2 → scanitd-0.9.3}/tests/__init__.py +0 -0
  62. {scanitd-0.9.2 → scanitd-0.9.3}/tests/conftest.py +0 -0
  63. {scanitd-0.9.2 → scanitd-0.9.3}/tests/test_basic.py +0 -0
  64. {scanitd-0.9.2 → scanitd-0.9.3}/tests/test_basic_read.py +0 -0
  65. {scanitd-0.9.2 → scanitd-0.9.3}/tests/test_cigar.py +0 -0
  66. {scanitd-0.9.2 → scanitd-0.9.3}/tests/test_scanitd.py +0 -0
  67. {scanitd-0.9.2 → scanitd-0.9.3}/tests/test_vcf_writer.py +0 -0
  68. {scanitd-0.9.2 → scanitd-0.9.3}/tox.ini +0 -0
@@ -0,0 +1,88 @@
1
+ name: Release to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ test:
10
+ name: Run test suite
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v3
17
+
18
+ - name: Install dependencies
19
+ run: uv sync --group dev
20
+
21
+ - name: Run tests
22
+ run: uv run pytest
23
+
24
+ build_sdist:
25
+ name: Build source distribution
26
+ needs: test
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@v3
33
+
34
+ - name: Build sdist
35
+ run: uv build --sdist
36
+
37
+ - name: Upload sdist artifact
38
+ uses: actions/upload-artifact@v4
39
+ with:
40
+ name: pypi-sdist
41
+ path: dist/
42
+
43
+ build_wheels:
44
+ name: Build wheels (${{ matrix.os }})
45
+ needs: test
46
+ runs-on: ${{ matrix.os }}
47
+ strategy:
48
+ matrix:
49
+ os: [ubuntu-latest, macos-latest]
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+
53
+ - name: Build wheels with cibuildwheel
54
+ uses: pypa/cibuildwheel@v4.3.5
55
+ with:
56
+ output-dir: dist
57
+
58
+ - name: Upload wheel artifacts
59
+ uses: actions/upload-artifact@v4
60
+ with:
61
+ name: pypi-wheels-${{ matrix.os }}
62
+ path: dist/
63
+
64
+ publish:
65
+ name: Publish to PyPI
66
+ needs: [build_sdist, build_wheels]
67
+ runs-on: ubuntu-latest
68
+ environment:
69
+ name: pypi
70
+ url: https://pypi.org/p/scanitd
71
+ permissions:
72
+ id-token: write
73
+ steps:
74
+ - name: Download sdist artifact
75
+ uses: actions/download-artifact@v4
76
+ with:
77
+ name: pypi-sdist
78
+ path: dist/
79
+
80
+ - name: Download wheel artifacts
81
+ uses: actions/download-artifact@v4
82
+ with:
83
+ pattern: pypi-wheels-*
84
+ path: dist/
85
+ merge-multiple: true
86
+
87
+ - name: Publish to PyPI
88
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -3,8 +3,9 @@ __pycache__/
3
3
  *.py[cod]
4
4
  *$py.class
5
5
 
6
- # C extensions
6
+ # C extensions and object files
7
7
  *.so
8
+ *.o
8
9
 
9
10
  # Distribution / packaging
10
11
  .Python
@@ -116,4 +117,6 @@ ENV/
116
117
  # Tool configurations
117
118
  .omc/
118
119
  .serena/
120
+ BIOCONDA.md
121
+ CLAUDE.md
119
122
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: scanitd
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: ScanITD
5
5
  Project-URL: Homepage, https://github.com/ylab-hi/ScanITD
6
6
  Project-URL: Repository, https://github.com/ylab-hi/ScanITD
@@ -22,7 +22,6 @@ Requires-Dist: psutil>=5.9.5
22
22
  Requires-Dist: pyfaidx>=0.7.2
23
23
  Requires-Dist: pysam>=0.22.0
24
24
  Requires-Dist: rich>=13.7.0
25
- Requires-Dist: ssw-py>=1.0.1
26
25
  Requires-Dist: typer>=0.12.5
27
26
  Provides-Extra: docs
28
27
  Requires-Dist: myst-parser==3.0.0; extra == 'docs'
@@ -35,15 +34,33 @@ Description-Content-Type: text/markdown
35
34
  # ScanITD
36
35
 
37
36
  [![PyPI version](https://img.shields.io/pypi/v/scanitd.svg)](https://pypi.python.org/pypi/scanitd)
38
- [![PyPI - Wheel](https://img.shields.io/pypi/wheel/scanitd)](https://pypi.org/project/scanitd/#files)
37
+ [![Conda Version](https://img.shields.io/conda/vn/bioconda/scanitd.svg)](https://anaconda.org/bioconda/scanitd)
38
+ [![PyPI - Wheel](https://img.shields.io/pypi/wheel/scanitd)](https://pypi.python.org/pypi/scanitd/#files)
39
39
  [![license](https://img.shields.io/pypi/l/scanitd.svg)](https://github.com/ylab-hi/ScanITD/blob/main/LICENSE)
40
40
  [![Documentation Status](https://readthedocs.org/projects/scanitd/badge/?version=latest)](https://scanitd.readthedocs.io)
41
41
 
42
42
 
43
43
  # 📦 Installation
44
44
 
45
- ScanITD can be installed using pip, the Python package installer.
46
- Follow these steps to install:
45
+ ScanITD can be installed using **Conda** (via the Bioconda channel) or **pip**.
46
+
47
+ ### Via Conda (Bioconda)
48
+
49
+ To install ScanITD using Conda:
50
+
51
+ ```bash
52
+ conda install -c bioconda scanitd
53
+ ```
54
+
55
+ Or using the modern `bioconda::scanitd` specifier:
56
+
57
+ ```bash
58
+ conda install bioconda::scanitd
59
+ ```
60
+
61
+ ### Via Pip
62
+
63
+ ScanITD can also be installed using pip, the Python package installer:
47
64
 
48
65
  1. Ensure you have Python 3.10 or later installed on your system.
49
66
 
@@ -60,11 +77,13 @@ Follow these steps to install:
60
77
  pip install scanitd
61
78
  ```
62
79
 
63
- 4. Verify the installation:
80
+ ### Verification
64
81
 
65
- ```bash
66
- scanitd --help
67
- ```
82
+ Verify the installation:
83
+
84
+ ```bash
85
+ scanitd --help
86
+ ```
68
87
 
69
88
  # 🛠️ Usage
70
89
 
@@ -1,15 +1,33 @@
1
1
  # ScanITD
2
2
 
3
3
  [![PyPI version](https://img.shields.io/pypi/v/scanitd.svg)](https://pypi.python.org/pypi/scanitd)
4
- [![PyPI - Wheel](https://img.shields.io/pypi/wheel/scanitd)](https://pypi.org/project/scanitd/#files)
4
+ [![Conda Version](https://img.shields.io/conda/vn/bioconda/scanitd.svg)](https://anaconda.org/bioconda/scanitd)
5
+ [![PyPI - Wheel](https://img.shields.io/pypi/wheel/scanitd)](https://pypi.python.org/pypi/scanitd/#files)
5
6
  [![license](https://img.shields.io/pypi/l/scanitd.svg)](https://github.com/ylab-hi/ScanITD/blob/main/LICENSE)
6
7
  [![Documentation Status](https://readthedocs.org/projects/scanitd/badge/?version=latest)](https://scanitd.readthedocs.io)
7
8
 
8
9
 
9
10
  # 📦 Installation
10
11
 
11
- ScanITD can be installed using pip, the Python package installer.
12
- Follow these steps to install:
12
+ ScanITD can be installed using **Conda** (via the Bioconda channel) or **pip**.
13
+
14
+ ### Via Conda (Bioconda)
15
+
16
+ To install ScanITD using Conda:
17
+
18
+ ```bash
19
+ conda install -c bioconda scanitd
20
+ ```
21
+
22
+ Or using the modern `bioconda::scanitd` specifier:
23
+
24
+ ```bash
25
+ conda install bioconda::scanitd
26
+ ```
27
+
28
+ ### Via Pip
29
+
30
+ ScanITD can also be installed using pip, the Python package installer:
13
31
 
14
32
  1. Ensure you have Python 3.10 or later installed on your system.
15
33
 
@@ -26,11 +44,13 @@ Follow these steps to install:
26
44
  pip install scanitd
27
45
  ```
28
46
 
29
- 4. Verify the installation:
47
+ ### Verification
30
48
 
31
- ```bash
32
- scanitd --help
33
- ```
49
+ Verify the installation:
50
+
51
+ ```bash
52
+ scanitd --help
53
+ ```
34
54
 
35
55
  # 🛠️ Usage
36
56
 
@@ -0,0 +1,53 @@
1
+ {% set name = "scanitd" %}
2
+ {% set version = "0.9.3" %}
3
+
4
+ package:
5
+ name: {{ name|lower }}
6
+ version: {{ version }}
7
+
8
+ source:
9
+ url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/scanitd-{{ version }}.tar.gz
10
+ sha256: 554efdf1c206a332fca1933a2ffb765d26127e22aaacadf86d170d21cbe288a6 # TODO: update sha256 after PyPI upload of 0.9.3
11
+
12
+ build:
13
+ number: 0
14
+ run_exports:
15
+ - {{ pin_subpackage('scanitd', max_pin="x.x") }}
16
+ script: {{ PYTHON }} -m pip install . -vv --no-deps --no-build-isolation
17
+ entry_points:
18
+ - scanitd = scanitd.cli.cli:app
19
+
20
+ requirements:
21
+ build:
22
+ - {{ compiler('c') }}
23
+ - make
24
+ host:
25
+ - python >=3.9
26
+ - pip
27
+ - hatchling
28
+ run:
29
+ - python >=3.9
30
+ - numpy >=2.0.0
31
+ - pysam >=0.22.0
32
+ - pyfaidx >=0.7.2
33
+ - psutil >=5.9.5
34
+ - loguru >=0.7.0
35
+ - typer >=0.12.5
36
+ - rich >=13.7.0
37
+
38
+ test:
39
+ commands:
40
+ - scanitd --help
41
+
42
+ about:
43
+ home: https://github.com/ylab-hi/ScanITD
44
+ summary: Detecting internal tandem duplication with robust variant allele frequency estimation
45
+ license: MIT
46
+ license_file: LICENSE
47
+ doc_url: https://scanitd.readthedocs.io
48
+
49
+ extra:
50
+ identifiers:
51
+ - doi:10.1093/gigascience/giaa089
52
+ recipe-maintainers:
53
+ - dolittle007
@@ -0,0 +1,81 @@
1
+ # Changelog
2
+
3
+ All notable changes to ScanITD will be documented in this file.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [0.9.3] — 2026-11-13
11
+
12
+ ### Fixed
13
+ - Removed the 8,000x `pysam.pileup()` depth cap that caused AF underestimation on deep amplicon data (now uncapped by default via `2**31 - 1`, configurable with `--max-depth`)
14
+ - Converted 0-based BED start coordinates (`start + 1`) to 1-based samtools region strings in `parse_target_genomic_coordinates()`, preventing `ValueError: start out of range (-1)` when a BED interval starts at `0`
15
+
16
+ ### Changed
17
+ - Vendored the MIT-licensed Striped Smith-Waterman (SSW) C library and `ctypes` Python wrapper under `src/scanitd/ssw`, removing the external `ssw-py` dependency
18
+ - Added a Hatchling custom build hook (`src/scanitd/ssw/build_ssw.py`) to compile `ssw.so` from source during wheel builds (with `sse2neon.h` support for ARM64)
19
+ - Updated `conda/meta.yaml` for native C extension builds (`{{ compiler('c') }}`, `make`) and removed `ssw-py`
20
+
21
+ ### Added
22
+ - Unit tests for `scanitd.ssw.AlignmentMgr` and `Alignment` (`tests/test_ssw.py`)
23
+ - Unit and end-to-end split-read rescue tests for `scanitd.inference.sr_resuer` (`tests/test_sr_resuer.py`)
24
+
25
+ ---
26
+
27
+ ## [0.9.2] — 2026-07-16
28
+
29
+ ### Fixed
30
+ - Filtering options were not applied (#11)
31
+ - Potential error in `inference/main.py` (#10)
32
+ - Various bug fixes across the `base`, `inference`, and `writer` modules
33
+
34
+ ### Changed
35
+ - Releases now publish to PyPI via GitHub Actions using
36
+ [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) (OIDC),
37
+ replacing manual `twine upload`
38
+ - Removed `poetry.lock` in favor of a single `pyproject.toml`/`uv.lock`-based
39
+ dependency setup
40
+ - Updated Python version requirements and dependencies
41
+ - Bumped dev dependencies: `pip` (19.2.3 → 26.1), `wheel` (0.33.6 → 0.38.1)
42
+
43
+ ### Added
44
+ - Comprehensive test suite
45
+
46
+ ---
47
+
48
+ ## [0.9.1] — 2024-11-20
49
+
50
+ ### Added
51
+ - Full Google-style docstrings across all modules (`base`, `inference`, `writer`, `cli`)
52
+ - Sphinx documentation with `autodoc`, `napoleon`, `sphinx-click`, and RTD theme
53
+ - User guide pages: Installation, Quick Start, Usage, Output format
54
+ - `sphinx-rtd-theme` and `sphinx-click` added to dev dependencies
55
+
56
+ ### Changed
57
+ - Dev dependency group reduced from 38 to 19 packages by removing redundant
58
+ flake8 ecosystem (replaced by ruff), `black`, `isort`, `reorder-python-imports`,
59
+ `pynvim`, `cohesion`, `nox`, `xdoctest`, and `safety`
60
+ - `[tool.isort]` config section removed from `pyproject.toml`
61
+
62
+ ---
63
+
64
+ ## [0.9.0] — 2024-11-08
65
+
66
+ ### Added
67
+ - Initial public release of ScanITD 2.0
68
+ - Chimeric-read (SA-tag) based TDUP detection
69
+ - Large-insertion (CIGAR-I) based TDUP/INS detection
70
+ - Split-read rescue via Smith-Waterman alignment (ssw-py)
71
+ - VCF 4.3 output with `OAO`, `AO`, `AF`, `INSSEQ`, `HOMSEQ` INFO fields
72
+ - Typer-based CLI with `--target` BED/region support
73
+ - Loguru-based structured logging
74
+
75
+ ---
76
+
77
+ ## Citation
78
+
79
+ Wang TY. and Yang R. [ScanITD: Detecting internal tandem duplication with robust
80
+ variant allele frequency estimation](https://doi.org/10.1093/gigascience/giaa089).
81
+ *GigaScience*, 2020.
@@ -13,7 +13,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "src"))
13
13
  project = "ScanITD"
14
14
  copyright = "2024, Ting-You Wang"
15
15
  author = "Ting-You Wang"
16
- release = "0.9.1"
16
+ release = "0.9.3"
17
17
  version = "0.9"
18
18
 
19
19
  # -- General configuration ---------------------------------------------------
@@ -32,6 +32,7 @@ file and writes results in VCF 4.3 format.
32
32
  | `--depth` | `-d` | `10` | Minimum read depth at the locus |
33
33
  | `--vaf` | `-f` | `0.1` | Minimum variant allele frequency (0–1) |
34
34
  | `--length` | | `10` | Minimum ITD length in base pairs |
35
+ | `--max-depth` | | `0` | Maximum pileup read depth per position (`0` for unlimited) |
35
36
 
36
37
  ### Alignment sensitivity
37
38
 
@@ -78,7 +79,7 @@ insertions (INS).
78
79
  ### 3. Soft-clipped read rescue
79
80
 
80
81
  Soft-clipped reads that lack an SA tag are aligned against the expected
81
- duplicated reference sequence using Smith-Waterman alignment (ssw-py). Those
82
+ duplicated reference sequence using the vendored Smith-Waterman library (`scanitd.ssw`). Those
82
83
  whose alignment is end-anchored and passes the mismatch threshold are counted
83
84
  as additional supporting reads, improving VAF estimates.
84
85
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "scanitd"
3
- version = "0.9.2"
3
+ version = "0.9.3"
4
4
  description = "ScanITD"
5
5
  authors = [
6
6
  { name = "Ting-You Wang", email = "tywang@northwestern.edu" },
@@ -21,7 +21,7 @@ dependencies = [
21
21
  "pysam>=0.22.0",
22
22
  "pyfaidx>=0.7.2",
23
23
  "psutil>=5.9.5",
24
- "ssw-py>=1.0.1",
24
+ # ssw-py is vendored directly under src/scanitd/ssw
25
25
  "loguru>=0.7.0",
26
26
  "typer>=0.12.5",
27
27
  "rich>=13.7.0",
@@ -74,6 +74,17 @@ build-backend = "hatchling.build"
74
74
 
75
75
  [tool.hatch.build.targets.wheel]
76
76
  packages = ["src/scanitd"]
77
+ exclude = ["*.o"]
78
+
79
+ [tool.hatch.build.hooks.custom]
80
+ path = "src/scanitd/ssw/build_ssw.py"
81
+
82
+
83
+ [tool.cibuildwheel]
84
+ build = ["cp310-*", "cp311-*", "cp312-*", "cp313-*"]
85
+ skip = ["*-musllinux*", "*_i686"]
86
+ test-command = "scanitd --version && python -c \"from scanitd.ssw import AlignmentMgr; m = AlignmentMgr(); m.set_read('ACGT'); m.set_reference('TTACGTTT'); r = m.align(); assert r.CIGAR == '4M' and r.reference_start == 2\""
87
+
77
88
 
78
89
  [tool.uv]
79
90
  package = true
@@ -1,5 +1,5 @@
1
1
  """Top-level package for ScanITD."""
2
2
 
3
3
  __author__ = """Ting-You Wang"""
4
- __version__ = "0.9.2"
4
+ __version__ = "0.9.3"
5
5
  __PACKAGE_NAME__ = "ScanITD"
@@ -19,6 +19,14 @@ def itd_len_type(value: int) -> int:
19
19
  return int(value)
20
20
 
21
21
 
22
+ def max_depth_type(value: int) -> int:
23
+ """Validate that max pileup depth is non-negative."""
24
+ if int(value) < 0:
25
+ msg = "max-depth must be non-negative (0 for unlimited)"
26
+ raise typer.BadParameter(msg)
27
+ return int(value)
28
+
29
+
22
30
  def version_callback(value: bool):
23
31
  """Print the ScanITD version string and exit.
24
32
 
@@ -119,6 +127,12 @@ def main(
119
127
  "--target",
120
128
  help="Limit analysis to targets listed in the BED-format file or a samtools region string",
121
129
  ),
130
+ max_depth: int = typer.Option(
131
+ 0,
132
+ "--max-depth",
133
+ callback=max_depth_type,
134
+ help="Maximum pileup read depth per position (0 for unlimited)",
135
+ ),
122
136
  log_level: LogLevel = typer.Option(LogLevel.INFO, "-l", "--log-level", help="set the logging level."),
123
137
  version: bool | None = typer.Option(
124
138
  None,
@@ -143,6 +157,7 @@ def main(
143
157
  mismatch_sr: Maximum mismatches for soft-read rescue alignment (default: 1).
144
158
  mismatch_insertion: Maximum mismatches for insertion-inferred duplication (default: 2).
145
159
  target: BED file path or samtools region string to restrict analysis.
160
+ max_depth: Maximum pileup read depth per position; 0 means unlimited (default: 0).
146
161
  log_level: Logging verbosity level (default: INFO).
147
162
  version: When provided, prints version and exits.
148
163
  """
@@ -165,6 +180,7 @@ def main(
165
180
  allowed_mismatches_for_sr_rescue=mismatch_sr,
166
181
  allowed_mismatches_for_insertion=mismatch_insertion,
167
182
  logger=logger,
183
+ max_depth=max_depth,
168
184
  )
169
185
 
170
186
  write_events_to_vcf(output, bam_header, event_list, logger, min_ao=ao, min_depth=dp, min_vaf=vaf)
@@ -352,11 +352,16 @@ def parse_target_genomic_coordinates(input_data):
352
352
  return result
353
353
 
354
354
  # Helper function to validate and convert coordinates
355
- def validate_coordinates(chrom, start, end):
355
+ def validate_coordinates(chrom, start, end, *, is_bed: bool = False):
356
356
  try:
357
357
  start = int(start)
358
358
  end = int(end)
359
- if start < 0 or end < 0 or start >= end:
359
+ if is_bed:
360
+ # BED is 0-based half-open [start, end); convert to 1-based inclusive chrom:start-end
361
+ if start < 0 or end < 0 or start >= end:
362
+ raise ValueError
363
+ return f"{chrom}:{start + 1}-{end}"
364
+ if start < 1 or end < 1 or start > end:
360
365
  raise ValueError
361
366
  return f"{chrom}:{start}-{end}"
362
367
  except ValueError:
@@ -374,7 +379,7 @@ def parse_target_genomic_coordinates(input_data):
374
379
  if line.strip() and not line.startswith("#"):
375
380
  fields = line.strip().split("\t")
376
381
  if len(fields) >= 3:
377
- result.append(validate_coordinates(fields[0], fields[1], fields[2]))
382
+ result.append(validate_coordinates(fields[0], fields[1], fields[2], is_bed=True))
378
383
  else:
379
384
  msg = f"Invalid BED format in line: {line}"
380
385
  raise ValueError(msg)
@@ -396,7 +401,7 @@ def parse_target_genomic_coordinates(input_data):
396
401
  try:
397
402
  chrom, pos = entry.split(":")
398
403
  start, end = pos.split("-")
399
- result.append(validate_coordinates(chrom, start, end))
404
+ result.append(validate_coordinates(chrom, start, end, is_bed=False))
400
405
  except ValueError:
401
406
  msg = f"Invalid chrom:start-end format: {entry}"
402
407
  raise ValueError(msg)
@@ -405,7 +410,7 @@ def parse_target_genomic_coordinates(input_data):
405
410
  elif "\t" in entry:
406
411
  fields = entry.split("\t")
407
412
  if len(fields) >= 3:
408
- result.append(validate_coordinates(fields[0], fields[1], fields[2]))
413
+ result.append(validate_coordinates(fields[0], fields[1], fields[2], is_bed=True))
409
414
  else:
410
415
  msg = f"Invalid BED format: {entry}"
411
416
  raise ValueError(msg)
@@ -227,6 +227,9 @@ class BamScanner:
227
227
  return self.tdup_anchors
228
228
 
229
229
 
230
+ _PYSAM_UNLIMITED_DEPTH = 2**31 - 1
231
+
232
+
230
233
  def scan_itd(
231
234
  in_bam_path,
232
235
  mapq_cutoff,
@@ -237,6 +240,7 @@ def scan_itd(
237
240
  allowed_mismatches_for_insertion,
238
241
  logger,
239
242
  microinsertion_cutoff: int = 10,
243
+ max_depth: int = 0,
240
244
  ):
241
245
  """Run the full ScanITD detection pipeline on a BAM file.
242
246
 
@@ -258,12 +262,19 @@ def scan_itd(
258
262
  logger: Logger instance implementing LoggerType.
259
263
  microinsertion_cutoff: Maximum microinsertion length at a breakpoint
260
264
  (default: 10).
265
+ max_depth: Maximum pileup read depth per position; 0 means unlimited
266
+ (default: 0).
261
267
 
262
268
  Returns:
263
269
  tuple: A 2-tuple of (sorted_event_list, bam_header) where sorted_event_list
264
270
  is a list of :class:`~scanitd.base.Event` objects sorted by (chrom, ref_start)
265
271
  and bam_header is the raw BAM header dict.
266
272
  """
273
+ if max_depth < 0:
274
+ msg = f"max_depth must be non-negative, got {max_depth}"
275
+ raise ValueError(msg)
276
+ effective_max_depth = _PYSAM_UNLIMITED_DEPTH if max_depth == 0 else max_depth
277
+
267
278
  regions = parse_target_genomic_coordinates(target_file)
268
279
 
269
280
  if len(regions) == 0:
@@ -297,7 +308,7 @@ def scan_itd(
297
308
 
298
309
  for _region in regions:
299
310
  try:
300
- for pileup_column in bam_object.pileup(region=_region, stepper="all", truncate=True):
311
+ for pileup_column in bam_object.pileup(region=_region, stepper="all", truncate=True, max_depth=effective_max_depth):
301
312
  # a list of pysam.PileupRead
302
313
  for pileup_read in pileup_column.pileups:
303
314
  position_of_pileup_site = pileup_read.query_position
@@ -1,6 +1,6 @@
1
1
  """Split-read rescue functions for augmenting tandem duplication allele observations.
2
2
 
3
- Uses Smith-Waterman local alignment (ssw-py) to rescue soft-clipped reads
3
+ Uses vendored Smith-Waterman local alignment (scanitd.ssw) to rescue soft-clipped reads
4
4
  that support a TDUP event but lack an SA tag, improving allele frequency estimates.
5
5
  """
6
6
 
@@ -8,20 +8,21 @@ from __future__ import annotations
8
8
 
9
9
  from typing import TYPE_CHECKING
10
10
 
11
- from ssw import AlignmentMgr
12
-
13
11
  from scanitd.base import MappingMode
12
+ from scanitd.ssw import AlignmentMgr
14
13
 
15
14
  if TYPE_CHECKING:
16
15
  from pyfaidx import Fasta
17
16
 
17
+ from scanitd.base import MicroRegion
18
+
18
19
  __all__ = [
19
20
  "update_tdup_ao",
20
21
  ]
21
22
 
22
23
 
23
24
  def update_tdup_ao(
24
- tdup_id: tuple[str, int, int, str],
25
+ tdup_id: tuple[str, int, int, str, MicroRegion],
25
26
  original_ao: int,
26
27
  genome_fasta: Fasta,
27
28
  to_be_rescued_sequences: dict,
@@ -0,0 +1,58 @@
1
+ ## ssw-py wrapper and utilities
2
+
3
+ Copyright (C) 2017 Wyss Institute at Harvard University
4
+ Copyright (C) 2023 Nick Conway
5
+
6
+ The MIT License
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+
26
+ http://www.opensource.org/licenses/mit-license.php
27
+
28
+ ## Complete-Striped-Smith-Waterman-Library (SSW C implementation)
29
+ https://github.com/mengyao/Complete-Striped-Smith-Waterman-Library
30
+
31
+ License: MIT
32
+
33
+ Copyright (c) 2012-2015 Boston College
34
+
35
+ Permission is hereby granted, free of charge, to any person obtaining a copy
36
+ of this software and associated documentation files (the "Software"), to deal
37
+ in the Software without restriction, including without limitation the rights
38
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39
+ copies of the Software, and to permit persons to whom the Software is
40
+ furnished to do so, subject to the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be included in
43
+ all copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
51
+ THE SOFTWARE.
52
+
53
+ Author:
54
+ C implementation: Mengyao Zhao
55
+
56
+ ## sse2neon.h (ARM64 NEON translation header)
57
+ https://github.com/DLTcollab/sse2neon
58
+ License: MIT
@@ -0,0 +1,17 @@
1
+ CC ?= gcc
2
+ CFLAGS = -O3 -fPIC -std=c99 -Wall -Wextra -I. $(ARCHFLAGS)
3
+ LDFLAGS = -lm
4
+ SRC = ssw.c str_util.c
5
+ OBJ = $(SRC:.c=.o)
6
+ TARGET = ssw.so
7
+
8
+ all: $(TARGET)
9
+
10
+ $(TARGET): $(OBJ)
11
+ $(CC) $(ARCHFLAGS) -shared -o $@ $^ $(LDFLAGS)
12
+
13
+ %.o: %.c ssw.h str_util.h
14
+ $(CC) $(CFLAGS) -c $< -o $@
15
+
16
+ clean:
17
+ rm -f $(OBJ) $(TARGET)