drvizer 0.1.1__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.
- drvizer-0.1.1/CHANGELOG.md +249 -0
- drvizer-0.1.1/LICENSE +21 -0
- drvizer-0.1.1/MANIFEST.in +5 -0
- drvizer-0.1.1/PKG-INFO +330 -0
- drvizer-0.1.1/README.md +297 -0
- drvizer-0.1.1/pyproject.toml +49 -0
- drvizer-0.1.1/setup.cfg +4 -0
- drvizer-0.1.1/setup.py +48 -0
- drvizer-0.1.1/src/drvizer/__init__.py +13 -0
- drvizer-0.1.1/src/drvizer/_cython_bed.c +10551 -0
- drvizer-0.1.1/src/drvizer/_cython_bed.pyx +142 -0
- drvizer-0.1.1/src/drvizer/_cython_gtf.c +8326 -0
- drvizer-0.1.1/src/drvizer/_cython_gtf.pyx +43 -0
- drvizer-0.1.1/src/drvizer/_cython_projection.c +7618 -0
- drvizer-0.1.1/src/drvizer/_cython_projection.pyx +43 -0
- drvizer-0.1.1/src/drvizer/_parallel.py +108 -0
- drvizer-0.1.1/src/drvizer/_track_build.py +224 -0
- drvizer-0.1.1/src/drvizer/api.py +1016 -0
- drvizer-0.1.1/src/drvizer/bam_parser.py +318 -0
- drvizer-0.1.1/src/drvizer/bed_parser.py +368 -0
- drvizer-0.1.1/src/drvizer/gtf_parser.py +683 -0
- drvizer-0.1.1/src/drvizer/utils.py +183 -0
- drvizer-0.1.1/src/drvizer/visualizer.py +784 -0
- drvizer-0.1.1/src/drvizer.egg-info/PKG-INFO +330 -0
- drvizer-0.1.1/src/drvizer.egg-info/SOURCES.txt +48 -0
- drvizer-0.1.1/src/drvizer.egg-info/dependency_links.txt +1 -0
- drvizer-0.1.1/src/drvizer.egg-info/requires.txt +9 -0
- drvizer-0.1.1/src/drvizer.egg-info/top_level.txt +1 -0
- drvizer-0.1.1/tests/test_bam_integration.py +79 -0
- drvizer-0.1.1/tests/test_bam_parallel.py +291 -0
- drvizer-0.1.1/tests/test_bed_parser_acceleration.py +409 -0
- drvizer-0.1.1/tests/test_build_parallel_preparation.py +298 -0
- drvizer-0.1.1/tests/test_get_transcript_data_snapshot.py +528 -0
- drvizer-0.1.1/tests/test_gtf_parser_acceleration.py +57 -0
- drvizer-0.1.1/tests/test_phase_2_2_lru_cache.py +459 -0
- drvizer-0.1.1/tests/test_phase_3_bam_kernel.py +175 -0
- drvizer-0.1.1/tests/test_phase_3_chunk_degradation.py +78 -0
- drvizer-0.1.1/tests/test_phase_3_cpu_guard.py +93 -0
- drvizer-0.1.1/tests/test_phase_3_crlf_bed.py +64 -0
- drvizer-0.1.1/tests/test_phase_3_error_chain.py +42 -0
- drvizer-0.1.1/tests/test_phase_7_degradation_guard.py +98 -0
- drvizer-0.1.1/tests/test_phase_7_imap_unordered.py +160 -0
- drvizer-0.1.1/tests/test_phase_7_version_and_metadata.py +89 -0
- drvizer-0.1.1/tests/test_phase_8_tuple_refactor.py +169 -0
- drvizer-0.1.1/tests/test_projection_acceleration.py +107 -0
- drvizer-0.1.1/tests/test_projection_invariants.py +178 -0
- drvizer-0.1.1/tests/test_split_by_transcript_api.py +860 -0
- drvizer-0.1.1/tests/test_split_by_transcript_visualizer.py +1037 -0
- drvizer-0.1.1/tests/test_version.py +80 -0
- drvizer-0.1.1/tests/test_visualizer_silent_bugs.py +399 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to drVizer are documented here. Versions follow
|
|
4
|
+
[Semantic Versioning](https://semver.org/). The format is based on
|
|
5
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
6
|
+
|
|
7
|
+
## [0.1.1] - 2026-09-02
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **CI matrix workflow** (`.github/workflows/ci.yml`): ubuntu-latest
|
|
11
|
+
on push to `main` and on `pull_request`, with a Python 3.8 - 3.12
|
|
12
|
+
matrix. Steps include a dedicated Cython build-prerequisites install
|
|
13
|
+
(mirroring `.github/workflows/benchmarks.yml`) so the editable
|
|
14
|
+
install compiles its Cython extensions on every runner, then
|
|
15
|
+
`pytest -q` for the test suite. macOS is intentionally excluded
|
|
16
|
+
from this matrix because Cython compilation on macOS is unvalidated
|
|
17
|
+
in the maintainer's Linux/DSR environment and risks a workflow that
|
|
18
|
+
fails on first push; it can be added in a follow-up phase once the
|
|
19
|
+
Linux matrix is proven stable across several push cycles.
|
|
20
|
+
- **Release workflow** (`.github/workflows/release.yml`): triggered by
|
|
21
|
+
push tags matching `v*`. Three sequential jobs: (1) `build` runs
|
|
22
|
+
`python -m build` and `twine check dist/*`; (2) `test-pypi` always
|
|
23
|
+
uploads the built artifact to TestPyPI as a dry-run / canary
|
|
24
|
+
validation step using the `TESTPYPI_API_TOKEN` secret; (3)
|
|
25
|
+
`publish-pypi` uploads to production PyPI using the
|
|
26
|
+
`PYPI_API_TOKEN` secret, gated on a `pypi` GitHub environment that
|
|
27
|
+
requires manual approval via environment protection rules. **No
|
|
28
|
+
auto-publish on first run.** The upload-artifact / download-artifact
|
|
29
|
+
pair is wired so the `dist/` directory flows from `build` to both
|
|
30
|
+
publish jobs.
|
|
31
|
+
- **Editable install gotcha section** in `CLAUDE.md`: documents that
|
|
32
|
+
switching git branches (especially after manual ref manipulation
|
|
33
|
+
such as `git update-ref refs/heads/main <sha>` or `git symbolic-ref
|
|
34
|
+
HEAD refs/heads/main`) leaves the editable install pointing at the
|
|
35
|
+
old `src/`. Mandated command:
|
|
36
|
+
`/datf/hanxi/software/miniconda3/envs/DRS/bin/pip install
|
|
37
|
+
--force-reinstall --no-deps -e .` after every merge.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- **Tuple-return worker contract** (P1-8, Phase 7 PARTIALLY-FIXED ->
|
|
41
|
+
FIXED; `src/drvizer/_parallel.py`): the
|
|
42
|
+
`_compute_region_coverage_with_path` worker now returns
|
|
43
|
+
`Tuple[str, np.ndarray]` -- `(bam_path, coverage_array)` -- so
|
|
44
|
+
per-bam-path attribution is encoded in the data shape instead of in
|
|
45
|
+
the closure binding that Phase 7 relied on. The master loop in
|
|
46
|
+
`aggregate_region_coverages_parallel` unpacks the tuple:
|
|
47
|
+
`for bam_path, coverage in pool.imap_unordered(...)`. This is a
|
|
48
|
+
**refactor for clarity and test maintainability**, not a
|
|
49
|
+
correctness fix: the Phase 7 closure binding was functionally
|
|
50
|
+
correct (the `bam_path` was bound into the worker closure, the
|
|
51
|
+
error message included the path, and master aggregation was
|
|
52
|
+
order-independent). v0.1.0's "Fixed: imap_unordered attribution
|
|
53
|
+
invariant" entry describes the original closure-binding contract;
|
|
54
|
+
v0.1.1's "Changed: tuple-return worker contract" describes the same
|
|
55
|
+
invariant re-expressed as a data shape. The worker still raises
|
|
56
|
+
`ParallelCoverageError(... from exc)` inside the worker so the
|
|
57
|
+
`__cause__` chain survives the `imap_unordered` boundary intact.
|
|
58
|
+
- **Test mocks updated** to match the new contract: 4 FakePool mocks
|
|
59
|
+
across `tests/test_bam_parallel.py` and
|
|
60
|
+
`tests/test_phase_3_cpu_guard.py` now return `(bam_path,
|
|
61
|
+
coverage_array)` tuples derived from `args[i][0]`, and the Phase 7
|
|
62
|
+
`tests/test_phase_7_imap_unordered.py` order-independence test
|
|
63
|
+
also returns `(bam_path, coverage_array)` tuples in reversed
|
|
64
|
+
arrival order. Net: the worker contract is exercised end-to-end in
|
|
65
|
+
every mock that previously returned a raw ndarray.
|
|
66
|
+
|
|
67
|
+
### Notes
|
|
68
|
+
- v0.1.0 was tagged locally only (commit `dd33199`) and was **never
|
|
69
|
+
pushed to PyPI**. v0.1.1 is therefore the first PyPI release of
|
|
70
|
+
drVizer without breaking any existing install.
|
|
71
|
+
- No throughput numbers or speedup claims appear in this release.
|
|
72
|
+
The GENCODE + ENCODE + pyGenomeTracks comparison is research-grade
|
|
73
|
+
work that belongs to a separate Stage 3 project, not to v0.1.1.
|
|
74
|
+
|
|
75
|
+
## [0.1.0] - 2026-09-01
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
- **`pyproject.toml` PEP 621 `[project]` section** (PKG-002): `name=drvizer`,
|
|
79
|
+
`version=0.1.0`, `requires-python>=3.8`, MIT license text, README,
|
|
80
|
+
Python 3.8 - 3.12 classifiers (incl. `Development Status :: 4 - Beta`),
|
|
81
|
+
`dependencies` from `requirements.txt`, optional `[bam]=[pysam]` and
|
|
82
|
+
`[test]=[pytest]` extras, and `project_urls`. Authoritative single
|
|
83
|
+
source of install / metadata truth.
|
|
84
|
+
- **`docs/api.md`**: public API contract document describing `DrViz`
|
|
85
|
+
constructor signature (`adaptive_threshold`, `cache_maxsize`), `plot()`,
|
|
86
|
+
`get_transcript_data()`, `ReusableParser` context manager,
|
|
87
|
+
`ParallelCoverageError` inheritance (`RuntimeError` -> subclass),
|
|
88
|
+
and the `__version__` contract.
|
|
89
|
+
- **Three new test files** (`tests/test_phase_7_*.py`):
|
|
90
|
+
- `test_phase_7_imap_unordered.py` verifies that the existing
|
|
91
|
+
`_compute_region_coverage_with_path` worker binds `bam_path` via
|
|
92
|
+
closure, the `ParallelCoverageError` raised on worker failure
|
|
93
|
+
carries the failing `bam_path` in its message, and the master
|
|
94
|
+
consumer's `imap_unordered` loop pairs `bam_path` to its coverage
|
|
95
|
+
correctly even when results arrive out of order.
|
|
96
|
+
- `test_phase_7_degradation_guard.py` asserts no `RuntimeWarning` is
|
|
97
|
+
emitted when zero chunks degraded and exactly one `RuntimeWarning`
|
|
98
|
+
is emitted with the degradation count in the message when chunks
|
|
99
|
+
degraded.
|
|
100
|
+
- `test_phase_7_version_and_metadata.py` asserts `drvizer.__version__`
|
|
101
|
+
matches `importlib.metadata.version("drvizer")` and both equal
|
|
102
|
+
`"0.1.0"`, and the `pyproject.toml` classifiers include Python
|
|
103
|
+
3.8 through 3.12 plus `Development Status :: 4 - Beta`.
|
|
104
|
+
|
|
105
|
+
### Changed
|
|
106
|
+
- **`setup.py` skeleton** (P1-16): `setup.py` is now a thin C-extension
|
|
107
|
+
compile-only wrapper. `extra_compile_args=["-std=c99"]` is preserved on
|
|
108
|
+
every Cython `Extension` block (panel template did not mention this;
|
|
109
|
+
dropping it would silently violate C99 compliance). All metadata
|
|
110
|
+
(name, version, classifiers, install_requires, extras_require,
|
|
111
|
+
project_urls, long_description) was moved to `pyproject.toml [project]`
|
|
112
|
+
so the two sources no longer drift.
|
|
113
|
+
- **`__version__` bumped `1.0.0` -> `0.1.0`** (PKG-002): first public
|
|
114
|
+
release tag.
|
|
115
|
+
- **GTF chunk-degradation warning gate** (P1-3): the end-of-parse
|
|
116
|
+
`RuntimeWarning` in `GTFParser.parse_gtf` is now Python-gated on
|
|
117
|
+
`if self._chunk_parse_degradation:` so clean parses emit no warning.
|
|
118
|
+
|
|
119
|
+
### Fixed
|
|
120
|
+
- **`imap_unordered` attribution invariant** (P1-8 verification, P0-8
|
|
121
|
+
follow-up): the worker `_compute_region_coverage_with_path` already
|
|
122
|
+
binds `bam_path = args[0]` via closure; the consumer's
|
|
123
|
+
`imap_unordered` loop relies on the per-`bam_path` `ParallelCoverageError`
|
|
124
|
+
message (which now embeds the path) instead of result order. The
|
|
125
|
+
invariant is locked in `tests/test_phase_7_imap_unordered.py`.
|
|
126
|
+
|
|
127
|
+
### Hygiene
|
|
128
|
+
- **Dead `BEDParser` import removed** from `src/drvizer/api.py:31`
|
|
129
|
+
(OCR-006): `BEDParser` is not referenced anywhere else in `api.py`;
|
|
130
|
+
tests import directly from `drvizer.bed_parser`. Net: zero callers
|
|
131
|
+
depend on the `api.py` re-export.
|
|
132
|
+
- **Dead `defaultdict` import removed** from `src/drvizer/utils.py:10`
|
|
133
|
+
(OCR-005): `defaultdict` is not referenced anywhere else in
|
|
134
|
+
`utils.py`.
|
|
135
|
+
- **Stale `docs/superpowers/` `.gitignore` rule removed** (DOC-001):
|
|
136
|
+
`docs/` does not contain a `superpowers/` directory; the rule was
|
|
137
|
+
leftover from a prior layout. The corresponding `workspace/`
|
|
138
|
+
superpowers material now lives under `/datf/hanxi/software/drVizer/workspace/`.
|
|
139
|
+
|
|
140
|
+
## [Unreleased] — Phase 2.2 (LRU cache + lifecycle)
|
|
141
|
+
|
|
142
|
+
### Added
|
|
143
|
+
- **LRU cache in `PreparedDataSource`** (Phase 2.2): an
|
|
144
|
+
`OrderedDict`-backed LRU cache keyed on
|
|
145
|
+
`(track_id, target_id, chrom, start, end)` so repeat
|
|
146
|
+
`get_transcript_data` / `plot` calls skip BED parse and BAM
|
|
147
|
+
coverage work. `cache_maxsize` defaults to 128; configure via
|
|
148
|
+
`DrViz(cache_maxsize=N)`.
|
|
149
|
+
- **`ReusableParser` lifecycle lock** (Phase 2.2): `__enter__`,
|
|
150
|
+
`__exit__`, `close()`, and `clear_cache()` methods. `close()` is
|
|
151
|
+
idempotent and runs the mandated order: `clear_cache` first, then
|
|
152
|
+
`pool.shutdown(wait=True)`, then flip `_is_closed`. The data
|
|
153
|
+
source rejects post-close inserts with `RuntimeError`.
|
|
154
|
+
- **`DrViz` context manager**: `with DrViz() as parser:` returns the
|
|
155
|
+
prepared `ReusableParser` (not the builder) so the cache and pool
|
|
156
|
+
are scoped to the block.
|
|
157
|
+
- **Adaptive `ProcessPool` threshold** (Phase 2.2): the build-stage
|
|
158
|
+
process pool is only opened when the genomic-BED record estimate
|
|
159
|
+
exceeds `adaptive_threshold` (default 20_000). Under the threshold
|
|
160
|
+
the path is pure sequential and no pool is allocated. Configure via
|
|
161
|
+
`DrViz(adaptive_threshold=N)`.
|
|
162
|
+
- **BAM dtype compression**: cached coverage payloads are stored as
|
|
163
|
+
`np.uint32` for `aggregate_method='sum'` and `np.float32` for
|
|
164
|
+
`aggregate_method='mean'`.
|
|
165
|
+
- `benchmarks/bench_cache_vs_no_cache.py`: stdlib-only benchmark
|
|
166
|
+
measuring the cache hit/miss speedup across `small/medium/large`
|
|
167
|
+
BED workloads.
|
|
168
|
+
|
|
169
|
+
### Changed
|
|
170
|
+
- `DrViz.build()` is now cached: successive calls with the same
|
|
171
|
+
builder state return the same `ReusableParser` instance so the
|
|
172
|
+
LRU cache survives. Any mutating method (`load_gtf`,
|
|
173
|
+
`add_bed_track`, `add_bam_track`) calls `_mark_dirty()` and forces
|
|
174
|
+
a recompile on the next `build()`.
|
|
175
|
+
- `DrViz.plot()` and `DrViz.get_transcript_data()` route through
|
|
176
|
+
`self.build()` instead of constructing a fresh `ReusableParser`
|
|
177
|
+
per call.
|
|
178
|
+
- `drvizer._track_build.prepare_tracks_parallel` accepts
|
|
179
|
+
`adaptive_threshold` and `data_source` keyword arguments; the
|
|
180
|
+
ProcessPool executor is owned by the data source so its lifecycle
|
|
181
|
+
is locked to the `ReusableParser.close()` path.
|
|
182
|
+
- The build-stage outer `ThreadPoolExecutor` wrapper around
|
|
183
|
+
process + serial paths has been removed; the two paths now run
|
|
184
|
+
sequentially within `prepare_tracks_parallel` (process first, then
|
|
185
|
+
serial), and the pool is gated by the threshold.
|
|
186
|
+
|
|
187
|
+
### Phase 3 — Cython/Python parity & hardening
|
|
188
|
+
|
|
189
|
+
#### Added
|
|
190
|
+
- **`ParallelCoverageError` re-exported from `drvizer` package**:
|
|
191
|
+
callers can now `from drvizer import ParallelCoverageError` for
|
|
192
|
+
typed error handling around parallel coverage failures.
|
|
193
|
+
- **`tests/fixtures/crlf.bed`**: Windows-style CRLF-terminated BED
|
|
194
|
+
fixture (PARSER-011) exercising comment + blank + multi-chrom
|
|
195
|
+
records.
|
|
196
|
+
- **`GTFParser._chunk_parse_degradation` counter**: incremented each
|
|
197
|
+
time the Cython chunk parser falls back to per-row Python parsing
|
|
198
|
+
(P1-3).
|
|
199
|
+
- **Five new test files** (`tests/test_phase_3_*.py`) covering CRLF
|
|
200
|
+
BED parity, GTF chunk degradation, BAM projection kernel
|
|
201
|
+
consistency, `cpu_count()` None guard, and `ParallelCoverageError`
|
|
202
|
+
exception chain. Two additional tests (`test_close_failure_emits_*`,
|
|
203
|
+
`test_get_transcript_data_none_coord_safe`) extend the Phase 2.2
|
|
204
|
+
LRU-cache suite.
|
|
205
|
+
|
|
206
|
+
#### Changed
|
|
207
|
+
- **P1-8** (`_parallel.py`): the `Pool.imap_unordered` worker now
|
|
208
|
+
catches any `Exception`, re-raises as `ParallelCoverageError` chained
|
|
209
|
+
from the worker exception (`raise ... from exc`), and includes the
|
|
210
|
+
failing BAM path in the message. The original traceback is preserved
|
|
211
|
+
on `__cause__` so corrupt BAM / missing `.bai` failures stay
|
|
212
|
+
diagnosable.
|
|
213
|
+
- **P1-3** (`gtf_parser.py`, `_cython_gtf.pyx`): pattern (a) of the
|
|
214
|
+
Cython chunk try/except decision is implemented. The `_process_chunk`
|
|
215
|
+
wrapper calls `_parse_gtf_chunk_impl` inside `try/except`; on
|
|
216
|
+
chunk-level failure it falls back to per-row Python parsing with
|
|
217
|
+
per-row `try/except (ValueError, TypeError)`, increments
|
|
218
|
+
`self._chunk_parse_degradation`, and emits a single
|
|
219
|
+
`RuntimeWarning` at `parse_gtf()` return time. The Cython kernel
|
|
220
|
+
remains a pure best-effort transform with no C-level try/except.
|
|
221
|
+
- **P1-6** (`bam_parser.py`): extracted the
|
|
222
|
+
`_project_aligned_blocks_to_transcript(transcript_id, blocks,
|
|
223
|
+
gtf_parser, target_chrom, region_start, region_len, coverage)`
|
|
224
|
+
pure-numeric kernel; both the single-transcript and the batch
|
|
225
|
+
transcript-coverage paths now route through this single helper so
|
|
226
|
+
spliced-read / int64 / `np.add.at` fixes live in one place.
|
|
227
|
+
- **P1-5** (`_parallel.py`, `_track_build.py`): `cpu_count()` is now
|
|
228
|
+
guarded with `try/except NotImplementedError` plus a
|
|
229
|
+
`isinstance(int)` fallback (and `os.cpu_count() or 1` in
|
|
230
|
+
`_track_build.py`). The original audit's `min(N, None, 32)` →
|
|
231
|
+
`TypeError` failure mode is impossible.
|
|
232
|
+
- **PARSER-011** (`_cython_bed.pyx`, `bed_parser.py`): BED open mode
|
|
233
|
+
unified. Cython path uses `raw_line.rstrip(b'\r\n')`; Python
|
|
234
|
+
fallback opens in `'rb'` and decodes UTF-8 with `errors='replace'`
|
|
235
|
+
before `rstrip('\r\n')` and tab-splitting. CRLF and LF BEDs now
|
|
236
|
+
produce byte-identical records across both paths.
|
|
237
|
+
- **Cleanup B** (`api.py`): the three `try/except Exception: pass`
|
|
238
|
+
blocks in `ReusableParser.close()` and the `__del__` finalizer now
|
|
239
|
+
surface failures as `warnings.warn(..., ResourceWarning,
|
|
240
|
+
stacklevel=2)` instead of silently swallowing them.
|
|
241
|
+
- **`PreparedDataSource._fetch_coverage_in_region` /
|
|
242
|
+
`_fetch_grouped_anno_in_region`** (`api.py`): the cache key now
|
|
243
|
+
preserves `None` for missing coordinates instead of raising
|
|
244
|
+
`TypeError` from `int(None)`; repeated calls with `start=None,
|
|
245
|
+
end=None` are now cache-hits.
|
|
246
|
+
|
|
247
|
+
## [1.0.0] - prior
|
|
248
|
+
|
|
249
|
+
Pre-Phase 2.2 release.
|
drvizer-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 drVizer Development Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
drvizer-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: drvizer
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A Python tool for parsing GTF/BED files and visualizing gene transcript structures
|
|
5
|
+
Author-email: x1han <han_xi@gzlab.ac.cn>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Bug Reports, https://github.com/x1han/drVizer/issues
|
|
8
|
+
Project-URL: Source, https://github.com/x1han/drVizer
|
|
9
|
+
Project-URL: Documentation, https://github.com/x1han/drVizer#readme
|
|
10
|
+
Keywords: bioinformatics,genomics,transcriptomics,gtf,bed,visualization
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: pandas>=1.3.0
|
|
26
|
+
Requires-Dist: matplotlib>=3.5.0
|
|
27
|
+
Requires-Dist: numpy>=1.21.0
|
|
28
|
+
Provides-Extra: bam
|
|
29
|
+
Requires-Dist: pysam; extra == "bam"
|
|
30
|
+
Provides-Extra: test
|
|
31
|
+
Requires-Dist: pytest; extra == "test"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# drVizer
|
|
35
|
+
|
|
36
|
+
`drVizer` is a Python library for building transcript-structure figures from GTF models, BED annotations, and BAM coverage tracks.
|
|
37
|
+
|
|
38
|
+
It is designed for direct RNA sequencing and transcriptomics workflows that need reusable, scriptable, publication-ready matplotlib figures.
|
|
39
|
+
|
|
40
|
+
## Breaking Fix in v0.1.0
|
|
41
|
+
|
|
42
|
+
- **`ParallelCoverageError` surfaces the BAM path on worker failure** (P1-8, merged in Phase 3): worker exceptions from `compute_region_coverage` are chained via `raise ... from exc` and include the failing `bam_path` in the message so corrupt BAM / missing `.bai` / `KeyError` / `BrokenPipeError` failures stay diagnosable.
|
|
43
|
+
- **GTF chunk-degradation warning now gated on `N > 0`** (P1-3): the end-of-parse `RuntimeWarning` only fires when `_chunk_parse_degradation > 0`. Clean parses emit no warning.
|
|
44
|
+
- **`pyproject.toml` PEP 621 `[project]` section** (PKG-002): `name=drvizer`, `version=0.1.0`, README, MIT, requires-python `>=3.8`, classifiers (Python 3.8 - 3.12 + Development Status 4 Beta), `dependencies` from `requirements.txt`, optional `[bam]` and `[test]` extras.
|
|
45
|
+
- **`setup.py` skeleton** (P1-16): `setup.py` is now a thin C-extension compile-only wrapper; all metadata truth lives in `pyproject.toml [project]`. `extra_compile_args=["-std=c99"]` is preserved on every Cython `Extension`.
|
|
46
|
+
- **Version bump `1.0.0` -> `0.1.0`** (PKG-002): first public release tag.
|
|
47
|
+
- **Hygiene**: dead `BEDParser` import removed from `src/drvizer/api.py`; dead `defaultdict` import removed from `src/drvizer/utils.py`; stale `docs/superpowers/` rule removed from `.gitignore`.
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- Build transcript structure figures from one or more GTF files.
|
|
52
|
+
- Overlay BED annotation tracks as interval blocks or numeric score bars.
|
|
53
|
+
- Add BAM-derived coverage tracks with `sum` or `mean` aggregation.
|
|
54
|
+
- Reuse parsed state across many plotting calls through a builder-style API.
|
|
55
|
+
- Project transcript-coordinate BED and BAM inputs back into genomic space.
|
|
56
|
+
- Split transcript-coordinate tracks into transcript-specific subtracks.
|
|
57
|
+
- Share automatic y-axis scaling across matched numeric tracks with `y_axis_group`.
|
|
58
|
+
- Export matplotlib figures for publication workflows.
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
Editable install for local development:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
git clone https://github.com/x1han/drVizer.git
|
|
66
|
+
cd drVizer
|
|
67
|
+
pip install -e .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Regular local install:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
BAM coverage tracks require `pysam`:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install pysam
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quick start
|
|
83
|
+
|
|
84
|
+
The public workflow is centered on `DrViz`:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from drvizer import DrViz
|
|
88
|
+
|
|
89
|
+
parser = (
|
|
90
|
+
DrViz()
|
|
91
|
+
.load_gtf("genes.gtf")
|
|
92
|
+
.add_bed_track("repeats.bed", label="TE")
|
|
93
|
+
.add_bam_track("reads.bam", label="Coverage")
|
|
94
|
+
.build()
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
fig = parser.plot("TP53", show=False)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`build()` freezes the configured GTF and tracks into a reusable parser, so plotting many genes does not repeat setup work.
|
|
101
|
+
|
|
102
|
+
### Reusable parser with auto-cleanup (Phase 2.2)
|
|
103
|
+
|
|
104
|
+
`DrViz` is also a context manager; using it as such hands back the
|
|
105
|
+
prepared `ReusableParser` and tears down the cache + lazy
|
|
106
|
+
`ProcessPool` on exit:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from drvizer import DrViz
|
|
110
|
+
|
|
111
|
+
with DrViz().load_gtf("genes.gtf").add_bed_track("repeats.bed", label="TE") as parser:
|
|
112
|
+
for gene in ("TP53", "BRCA1", "MYC"):
|
|
113
|
+
fig = parser.plot(gene, show=False)
|
|
114
|
+
# cache cleared, pool shut down with wait=True
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Cache and pool configuration:
|
|
118
|
+
|
|
119
|
+
- `DrViz(cache_maxsize=N)` — LRU cache capacity (default 128).
|
|
120
|
+
Caches are per-parser-instance; each new `build()` constructs a
|
|
121
|
+
fresh cache.
|
|
122
|
+
- `DrViz(adaptive_threshold=N)` — minimum total BED record count
|
|
123
|
+
that opens a `ProcessPool` during build (default 20_000). Below
|
|
124
|
+
the threshold, build is purely sequential.
|
|
125
|
+
|
|
126
|
+
## Core API
|
|
127
|
+
|
|
128
|
+
### `load_gtf(...)`
|
|
129
|
+
|
|
130
|
+
Load one or more GTF files. GTF parsing is required before tracks can be built.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
.load_gtf("genes.gtf")
|
|
134
|
+
.load_gtf(["reference.gtf", "novel_transcripts.gtf"])
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| Parameter | Type | Default | Description |
|
|
138
|
+
| --- | --- | --- | --- |
|
|
139
|
+
| `gtf_files` | `str` or `list[str]` | required | Path to one GTF file or ordered list of GTF files. Exon/CDS features are parsed for transcript rendering. Loading a new GTF resets previously added tracks because tracks depend on the active GTF model. |
|
|
140
|
+
|
|
141
|
+
drVizer uses exon/CDS features and supports gene ID, gene name, or transcript ID lookup during plotting.
|
|
142
|
+
|
|
143
|
+
### `add_bed_track(...)`
|
|
144
|
+
|
|
145
|
+
Add BED-backed annotation tracks.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
.add_bed_track("repeats.bed", label="TE", color="tomato")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
| Parameter | Type | Default | Description |
|
|
152
|
+
| --- | --- | --- | --- |
|
|
153
|
+
| `bed_files` | `str` or `list[str]` | required | BED file path or multiple BED files grouped into one logical track. BED3 and wider BED records are supported. |
|
|
154
|
+
| `label` | `str` or `None` | auto `Track_N` | Track label shown on the left side of the figure. Duplicate labels are made unique in registration order. |
|
|
155
|
+
| `color` | `str` or `list[str]` | `"orange"` | Matplotlib color for the track. When multiple BED files are passed, a list gives per-file colors. |
|
|
156
|
+
| `alpha` | `float` or `list[float]` | `0.8` | Track transparency from `0` to `1`. When multiple BED files are passed, a list gives per-file alpha values. |
|
|
157
|
+
| `parser_type` | `"distribution"` or `"score"` | `"distribution"` | Rendering mode. `distribution` draws interval blocks; `score` draws numeric BED scores as bars. |
|
|
158
|
+
| `y_axis_range` | `float` or `None` | `None` | Fixed y-axis maximum for `score` tracks. Takes precedence over automatic scaling and `y_axis_group`. |
|
|
159
|
+
| `y_axis_group` | `str` or `None` | `None` | Shared automatic y-axis scaling group for numeric BED `score` tracks. Invalid for `distribution` tracks. |
|
|
160
|
+
| `transcript_coord` | `bool` | `False` | Treat BED `chrom` field as transcript ID and project transcript coordinates back to genomic coordinates through the loaded GTF. |
|
|
161
|
+
| `layer_order` | `None`, `"ascending"`, or `"descending"` | `"ascending"` | Controls drawing order for layered BED elements. |
|
|
162
|
+
| `split_by_transcript` | `None`, `"nc"`, or `"cn"` | `None` | Split transcript-coordinate BED data into transcript-specific subtracks. Requires `transcript_coord=True`. |
|
|
163
|
+
|
|
164
|
+
Score tracks can share automatic y-axis scaling:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
.add_bed_track("control_m6a.bed", label="Control m6A", parser_type="score", y_axis_group="m6A")
|
|
168
|
+
.add_bed_track("treated_m6a.bed", label="Treated m6A", parser_type="score", y_axis_group="m6A")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### `add_bam_track(...)`
|
|
172
|
+
|
|
173
|
+
Add BAM-backed coverage tracks.
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
.add_bam_track("reads.bam", label="Coverage", color="steelblue")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
| Parameter | Type | Default | Description |
|
|
180
|
+
| --- | --- | --- | --- |
|
|
181
|
+
| `bam_files` | `str` or `list[str]` | required | BAM file path or multiple BAM files grouped into one logical coverage track. BAM support requires `pysam`. |
|
|
182
|
+
| `label` | `str` | `"Coverage"` | Track label shown on the left side of the figure. Duplicate labels are made unique in registration order. |
|
|
183
|
+
| `color` | `str` or `list[str]` | `"steelblue"` | Matplotlib color for coverage. When multiple BAM files are rendered as per-file series, a list gives per-file colors. |
|
|
184
|
+
| `alpha` | `float` or `list[float]` | `0.6` | Coverage transparency from `0` to `1`. When multiple BAM files are rendered as per-file series, a list gives per-file alpha values. |
|
|
185
|
+
| `aggregate_method` | `"sum"` or `"mean"` | `"sum"` | Combines multiple BAM files by summed coverage or average coverage. |
|
|
186
|
+
| `y_axis_range` | `float` or `None` | `None` | Fixed coverage y-axis maximum. Takes precedence over automatic scaling and `y_axis_group`. |
|
|
187
|
+
| `y_axis_group` | `str` or `None` | `None` | Shared automatic y-axis scaling group for numeric coverage tracks. |
|
|
188
|
+
| `transcript_coord` | `bool` | `False` | Treat BAM reference names as transcript IDs and project coverage back to genomic coordinates through the loaded GTF. |
|
|
189
|
+
| `layer_order` | `None`, `"ascending"`, or `"descending"` | `"ascending"` | Controls drawing order for per-file coverage series where individual series are rendered. |
|
|
190
|
+
| `split_by_transcript` | `None`, `"nc"`, or `"cn"` | `None` | Split transcript-coordinate BAM coverage into transcript-specific subtracks. Requires `transcript_coord=True`. |
|
|
191
|
+
|
|
192
|
+
Multiple BAM files can be combined:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
.add_bam_track(
|
|
196
|
+
["sample_a.bam", "sample_b.bam"],
|
|
197
|
+
label="Reads",
|
|
198
|
+
aggregate_method="mean",
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Supported aggregation modes:
|
|
203
|
+
|
|
204
|
+
- `aggregate_method="sum"`: sum coverage across BAM files.
|
|
205
|
+
- `aggregate_method="mean"`: average coverage across BAM files.
|
|
206
|
+
|
|
207
|
+
### `build()` and `plot(...)`
|
|
208
|
+
|
|
209
|
+
Use `build()` when plotting multiple genes from the same inputs:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
parser = (
|
|
213
|
+
DrViz()
|
|
214
|
+
.load_gtf("genes.gtf")
|
|
215
|
+
.add_bed_track("repeats.bed", label="TE")
|
|
216
|
+
.add_bam_track("reads.bam", label="Coverage")
|
|
217
|
+
.build()
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
fig1 = parser.plot("TP53", show=False)
|
|
221
|
+
fig2 = parser.plot("MYC", show=False)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Use one-shot `plot(...)` for quick figures:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
fig = (
|
|
228
|
+
DrViz()
|
|
229
|
+
.load_gtf("genes.gtf")
|
|
230
|
+
.add_bed_track("repeats.bed", label="TE")
|
|
231
|
+
.plot("TP53", show=False)
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`ReusableParser.plot(...)` accepts these parameters:
|
|
236
|
+
|
|
237
|
+
| Parameter | Type | Default | Description |
|
|
238
|
+
| --- | --- | --- | --- |
|
|
239
|
+
| `gene` | `str` or `list[str]` | required | Gene ID, gene name, transcript ID, or same-chromosome list of identifiers to plot. |
|
|
240
|
+
| `transcript_to_show` | `str`, `list[str]`, or `None` | `None` | Restrict output to one transcript or selected transcripts from the requested gene. |
|
|
241
|
+
| `output` | `str` or `None` | `None` | Optional output path. When set, figure is saved with matplotlib using tight bounding box and 300 DPI. |
|
|
242
|
+
| `figsize` | `tuple` or `None` | `None` | Explicit final figure size in inches. Overrides automatically computed size. |
|
|
243
|
+
| `figfact` | `tuple` or `None` | `None` | Multiplicative width/height factor applied to automatically computed figure size. Ignored when `figsize` is set. |
|
|
244
|
+
| `show` | `bool` | `True` | Display figure through matplotlib. If `False`, figure is closed after creation but still returned. |
|
|
245
|
+
| `close` | `bool` | `False` | Close figure after showing it. Applies when `show=True`. |
|
|
246
|
+
| `**kwargs` | any | | Forwarded to the visualizer, including transcript sorting and layout options. |
|
|
247
|
+
|
|
248
|
+
## Transcript-coordinate workflows
|
|
249
|
+
|
|
250
|
+
Set `transcript_coord=True` when BED or BAM records use transcript IDs instead of genomic chromosome names.
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
.add_bed_track("mods.transcript.bed", transcript_coord=True)
|
|
254
|
+
.add_bam_track("reads.transcript.bam", transcript_coord=True)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
drVizer projects transcript-coordinate intervals and coverage back into genomic plotting space through the loaded GTF model.
|
|
258
|
+
|
|
259
|
+
## Split transcript tracks
|
|
260
|
+
|
|
261
|
+
Transcript-coordinate BED and BAM tracks can be split by transcript with `split_by_transcript`:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
split_by_transcript="nc" # transcript-major order
|
|
265
|
+
split_by_transcript="cn" # track-major order
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Supported modes:
|
|
269
|
+
|
|
270
|
+
- `None`: keep combined track behavior.
|
|
271
|
+
- `"nc"`: transcript-major ordering; each transcript groups its split tracks together.
|
|
272
|
+
- `"cn"`: track-major ordering; each track groups its transcript-specific subtracks together.
|
|
273
|
+
|
|
274
|
+
Split transcript tracks require `transcript_coord=True` and do not support multi-gene plotting.
|
|
275
|
+
|
|
276
|
+
## Numeric y-axis control
|
|
277
|
+
|
|
278
|
+
Numeric tracks support two y-axis controls:
|
|
279
|
+
|
|
280
|
+
- `y_axis_range`: manually fix the y-axis maximum.
|
|
281
|
+
- `y_axis_group`: share automatic y-axis scaling across numeric tracks with the same group name.
|
|
282
|
+
|
|
283
|
+
`y_axis_range` takes precedence over `y_axis_group`.
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
parser = (
|
|
287
|
+
DrViz()
|
|
288
|
+
.load_gtf("genes.gtf")
|
|
289
|
+
.add_bam_track("control.bam", label="Control", y_axis_group="reads")
|
|
290
|
+
.add_bam_track("treated.bam", label="Treated", y_axis_group="reads")
|
|
291
|
+
.add_bed_track("m6a.bed", label="m6A", parser_type="score", y_axis_group="mod_score")
|
|
292
|
+
.build()
|
|
293
|
+
)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Output behavior
|
|
297
|
+
|
|
298
|
+
drVizer renders matplotlib figures and returns the generated `Figure` object from `plot(...)`.
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
fig = parser.plot("TP53", show=False, output="tp53.pdf")
|
|
302
|
+
fig.set_size_inches((10, 6))
|
|
303
|
+
fig.savefig("tp53.png", dpi=300, bbox_inches="tight")
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Documentation
|
|
307
|
+
|
|
308
|
+
Detailed docs live in `docs/`:
|
|
309
|
+
|
|
310
|
+
- [Project overview / PDR](docs/project-overview-pdr.md)
|
|
311
|
+
- [Codebase summary](docs/codebase-summary.md)
|
|
312
|
+
- [System architecture](docs/system-architecture.md)
|
|
313
|
+
- [API reference](docs/api-reference.md)
|
|
314
|
+
- [Testing guide](docs/testing-guide.md)
|
|
315
|
+
- [Code standards](docs/code-standards.md)
|
|
316
|
+
- [Changelog](docs/changelog.md)
|
|
317
|
+
|
|
318
|
+
## Testing
|
|
319
|
+
|
|
320
|
+
Use the project DRS environment for validation:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
/datf/hanxi/software/miniconda3/envs/DRS/bin/python -m pytest -q
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Public entry point
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
from drvizer import DrViz
|
|
330
|
+
```
|