Release Notes
Table of contents
- v2.0.0 — columnar only
- v1.1.0 — columnar foundation
- v1.0.1 — pip-install fix
- v1.0.0 — first stable release
- Upgrade notes (1.0)
v2.0.0 — columnar only
2.0.0 makes the columnar tables introduced in 1.1 the API. genomeblocks.Loci, Genes, Pairs, Architecture and Atlas are tables of numpy columns whose row number is the join key; the 1.x object API (Loci as a list of Locus, Gene / Transcript / Exon trees, list[Pair], Architecture as a graph_tool.Graph) is gone, and so is the genomeblocks.columnar subpackage — its contents are now the top-level names. There are no compatibility shims. Upgrade: pip install -U genomeblocks. The 1.1 documentation stays online: pick 1.1 in the version menu at the top of every page.
What 2.0 is
- Tables.
Loci(codes/starts/ends/strands+cols),Genes(three linkedLoci: genes, transcripts, features),Pairs(BEDPE: anchorsa,b+ columns),Architecture(vertices = aLoci, an edge table withepcolumns,vpvertex columns),Atlas(bins × tracks), plusLocus/LocusView,GeneViewandGenome. Every container hasshape,columns,head,tail,describe,_repr_html_. - One boundary, explicit exits.
gb.as_loci(x)turns a path (BED / narrowPeak / CSV / TSV / parquet), a region string, aLocus, pandas / polars / pyarrow / bioframe / PyRanges / pybedtools frames, dicts of columns, structured arrays, an AnnData or a list of regions into aLoci, and every public function calls it on its inputs. Out:to_pandas/to_polars/to_arrow/to_bioframe/to_pyranges/to_bedtool/to_cgranges/to_anndata/to_records/to_numpy/to_bed/save(parquet);Architectureaddsto_networkx/to_igraph/to_graph_tool/to_scipy. - Protocols. Arrow C stream, dataframe interchange and narwhals on every container, so polars, DuckDB, seaborn, plotly and altair take the objects as they are.
- Backends. Six families — intervals, bigwig, motifs, fasta, tables, graph — with a pip-installable default each and other engines on request (
backend=on a call,with gb.use_backend(...)).gb.backends()lists them. A requested backend that is missing raises with the install command; there is no silent fallback.benchmarks/bench_backends.pytimes every engine; the test suite checks they agree. - Default install is pure pip.
pip install genomeblocksgives every default backend except a motif engine (genomeblocks[motifs]adds MOODS,genomeblocks[lightmotif]the alternative engine; the conda package ships MOODS),Architectureincluded (graph algorithms run on scipy; graph-tool is picked up automatically when installed). Extras:fast,bam,viz,interop,all. - New dependencies:
narwhals,pyarrow. Dropped from core:pyranges(now an optional interval backend). - Also new:
Loci.from_anndata/to_anndata,Loci.liftover(pyliftover),Loci.sequences/to_seqrecords/to_fastaon any FASTA backend,Genes.from_frame/to_gtf/to_bed12/representative,Pairs.overlapping/filter,Architecture.from_edges/from_frame/from_scipy/pagerank/vertices_frame/drawon any graph backend,gb.load_motifsfor JASPAR / jaspar16 / TRANSFAC / uniprobe / MEME / Biopython / arrays, motif p-value thresholds,scan_motifs_profile,gb.plot_motif_heatmap,gb.igv_html,gb.View.
Breaking changes
- Coordinates. Every table is 0-based, half-open.
Genes.makeconverts GTF / GFF3 starts (start - 1); 1.x kept raw 1-based GTF starts. A gene’s TSS is the 1-bp interval[t, t+1)witht = starton+andt = end - 1on-; 1.x stored-strand TSSs end-before-start. - No global state. The session-wide default
Genomeis gone: each table carries its own, and operations between tables re-code the other side.set_backendis gone: usebackend=per call orwith gb.use_backend(...). - No legacy API.
Lociis not alist;Locusobjects are views (L[i]), not stored.Gene,Transcript,Exon,CDS,UTR,Pair,make_genome,scan_motifs(top level),Architecture.to_legacy,Loci.cgrandgenomeblocks.columnarno longer exist. Architecturemethods take nolociargument.make(loci, bedpe)fixes the vertices;add_mcool(mcool, resolution=...),normalize(),annotate(genes),strength(),prime_hubs()work on them.make(..., trans=True)keeps inter-chromosomal loops (they getep.d = infand are normalised against the mean trans weight);dmaxapplies to cis loops only.- Motif functions take
motifsandformat=, not a JASPAR path:motifsis a path, aLibrary, Biopython motifs or matrices;formatis'jaspar'(default),'jaspar16','transfac','uniprobe'or'meme'. Results are aligned to the input rows (index = uid); windows that cannot be read count 0.scan_motifs_profilereturns(M, names). signal(backend=...)names the bigWig engine:'pybigtools','pybigwig'or'python'(the pure-Python reader; 1.x called it'bigwig').- The browser draws from the tables:
browser(region, tracks)takes bigWig paths or handles, BAM paths,Pairsor.bedpepaths,Genes, and anythingas_locitakes.architecture_draw.draw(arch, loci, region)becameA.draw(region, layout=...). - Graph-tool is optional.
Architectureis not agraph_tool.Graph;A.graph()/A.to_graph_tool()build one on demand when graph-tool is installed.
Migration: 1.x → 2.0
| 1.x | 2.0 |
|---|---|
Loci([Locus(...), ...]), Loci(list_of_locus) |
Loci.from_records([...]) or gb.as_loci([...]) (tuples, region strings and Locus objects all work) |
Loci(list(genes.annot['prom'])) |
genes.get_tss().slop(genes.promoter_r) (already a Loci; genes.annot['prom'] is the merged version) |
loci.cgr (the cgranges index) |
gone — the interval engine is a backend: loci.intersect(o, backend="cgranges") or with gb.use_backend(intervals="cgranges"); loci.to_cgranges() builds the index for your own code |
loci.uids → {uid: Locus} |
loci.uids → {uid: row}; loci[uid] → a LocusView; loci.row(uid) |
for locus in loci: to build columns |
vectorised: loci.starts, loci.lengths, loci.centers, loci["score"]; loci["new"] = values |
Pair, read_bedpe(path) → list[Pair] |
Pairs.make(path) (a table; read_bedpe returns it too); P.a, P.b, P["score"], P.distance, P.is_cis |
pair_to_bed(pairs, loci) |
Pairs.overlapping(loci, r=..., both=...); Pairs.anchors_overlap(loci) for the two masks |
make_genome(fasta) |
gb.read_fasta(fasta) (a {chrom: sequence} dict) or Genome.from_fasta(fasta) (names and sizes); FASTA paths / dicts / handles are accepted directly by sequences, scan_motifs*, browser |
scan_motifs(loci, fasta, jaspar_path) |
loci.scan_motifs(fasta, motifs, format="jaspar") — motifs is a path, a Library (gb.load_motifs), Biopython motifs or matrices |
scan_motifs_matrix(..., jaspar=...) |
scan_motifs_matrix(loci, fasta, motifs, format=..., threshold=... or pvalue=...); the result index is the uid, aligned to the rows |
Architecture.make(cre, bedpe).add_mcool(cre, mcool, resolution=r) |
Architecture.make(cre, bedpe).add_mcool(mcool, resolution=r) |
arch.normalize(cre, source="w", name="n") |
A.normalize(source="w", name="n") |
arch.annotate(cre, genes) |
A.annotate(genes) |
arch.strength(key="n"), arch.prime_hubs(key="n") |
unchanged names; A.vp.strength, A.vp.gene, A.vp.annot are arrays aligned to A.loci |
arch.vp.uid[v], arch.vertex(uid) |
vertex i is A.loci row i: A.loci.uid[i], A.loci.row(uid); A[uid] and A.neighbors(uid) still answer by uid |
graph_tool.all on arch |
A.graph() / A.to_graph_tool() (graph-tool), A.to_networkx(), A.to_igraph(), A.to_scipy(); A.components(), A.pagerank() on any graph backend |
architecture_draw.draw(arch, loci, region) |
A.draw(region, layout="spring" | "circular" | "genomic", backend=None) |
Gene / Transcript / Exon / CDS / UTR objects, genes.genes[name].transcripts[...] |
the three tables genes.genes, genes.transcripts, genes.features (0-based) + GeneView: genes["TP53"].tss / .transcripts / .exons / .canonical; genes.to_pandas("transcripts") |
1-based GTF starts in Genes |
0-based: start - 1 is applied at parse time; Genes.from_frame(df, one_based=True) for a frame that still carries GTF starts |
- strand TSS stored as end with start > end |
[end - 1, end): genes.get_tss(), genes["X"].tss |
genes.annotations(loci) / nearest_genes(loci) |
unchanged; they take anything as_loci takes and return one row per input row |
import genomeblocks.columnar as gbc; gbc.Loci |
import genomeblocks as gb; gb.Loci — genomeblocks.columnar.* → genomeblocks.* |
gbc.Architecture.make(cre, "loops.bedpe").add_mcool(...) |
unchanged shape; Pairs replaces the BEDPE list, trans=True by default |
set_backend("pyranges") |
backend="pyranges" on the call, or with gb.use_backend(intervals="pyranges"): |
signal(loci, bws, backend="bigwig") (the pure-Python reader) |
loci.signal(bws, backend="python"); "pybigtools" (default) and "pybigwig" are the others |
signal(..., bigwig=...) with the genomeblocks.bigwig module |
the reader moved to genomeblocks.backends._bbi; use the backend= name |
compare_heatmap(a, b, bigwigs) |
unchanged; a and b are anything as_loci takes |
browser(region, {"loops": read_bedpe(p)}) |
browser(region, {"loops": "loops.bedpe"}) or a Pairs |
Atlas.make(beds), loci.enrich(atlas) |
unchanged; inputs go through as_loci, atlas.to_pandas() is the track table |
Loci.make(path) returning unsorted rows in file order |
Loci.make sorts into genome order (sort=False keeps file order); as_loci(path) keeps file order |
If a 1.x script breaks on
AttributeError: module 'genomeblocks' has no attribute ..., the name is in the table above.gb.__all__lists the whole 2.0 surface.
Also in 2.0
Loci.makereads narrowPeak / broadPeak columns under their standard names withkeep=True(or a list), refuses.gtf/.bedpewith a pointer to the right constructor, and reads.gz.Genes.make(..., chr_map={'1': 'chr1'})renames chromosomes while parsing; GFF3 and Ensembl spellings (five_prime_utr,gene_biotype) are recognised; duplicate gene ids are handled.Loci.tile/tile_genometake sizes from a dict, a.chrom.sizespath, a Series, aGenomeor a cooler.nearestreports the gap in bases (0 for overlaps and book-ended intervals) and(-1, -1)when the chromosome has nothing, on every backend.Loci.liftover(chain)walks each end inward by up to1 - min_matchof the length before dropping a row (UCSC’s base-fraction rule);cols['source_row']says where each row came from.select_isoformsandGenes.make(cre=, bw=)accept any peak input (a list of peak sets is merged) and bigWig paths or handles.count_pairs/count_pairs_2ddetect HiC-Pro, 4DN.pairsand Juicer layouts;read_pairs_chunksstreams any of them.se.call_sereturns the stitched regions as aLociwithrank,signalandis_secolumns;hichip.shortrange_trackwrites coverage bigWigs / MACS3 input from pairs files.- The one-file viewers (
igv_html,View) draw genes from the 0-based tables and signal through the bigWig backends.
v1.1.0 — columnar foundation
1.1.0 adds genomeblocks.columnar, a table-first foundation that sits next to
the classic API (which is unchanged). It also adds a benchmark suite and fixes
every slow path that suite found. Upgrade: pip install -U genomeblocks
(pip install -U "genomeblocks[columnar]" adds polars and pyarrow).
genomeblocks.columnar (new)
CREs, genes and the Architecture are numpy tables that share one Genome, and
the row number is the join key: row i of the CREs is row i of the labels,
the signal cube and every graph column.
import genomeblocks.columnar as gbc
cre = gbc.Loci.make("atac.narrowPeak")
genes = gbc.Genes.make("gencode.gtf") # GTF or GFF3
A = (gbc.Architecture.make(cre, "loops.bedpe")
.add_mcool("hic.mcool", resolution=5000)
.normalize().annotate(genes).strength())
A.chrom("chr8"); A.cis; A.trans # zero-copy views
Loci: genome-sorted columns, vectorised overlap / merge / nearest, and hand-off to pandas, polars, Arrow and parquet. Classic functions such assignal()run on it unchanged.Genes: genes ⇄ transcripts ⇄ features linked by row numbers, GTF and GFF3 (GENCODE and Ensembl style), and a vectorisedselect_isoforms.Architecture: one edge table sorted into per-chromosome cis blocks plus a trans block. Trans loops are kept and normalised against the mean trans weight. Includesnear/support, graph-tool on demand, and parquet save / load.hichip(short-range HiChIP ends → coverage bigWig / MACS3 input),se(ROSE-style super-enhancers), andview/igv(one-file shareable browsers).- Measured against 1.0.1, the whole pipeline in a fresh process takes 43 s → 2.7 s with identical edges, weights, O/E, labels and hubs, and peak memory drops from 1.26 GB to 0.78 GB.
- polars and pyarrow are optional: without them parsing falls back to pandas with identical results (parquet save / load needs pyarrow).
(In 2.0 these tables became the top-level API; genomeblocks.columnar no longer exists.)
Faster, and one crash fixed (classic API)
The new benchmarks/ suite times every block against the tools people would
otherwise use (results: Benchmarks). Every fix below was
checked to give output identical to 1.0.1.
Architecture.normalizeno longer crashes on inter-chromosomal edges. Trans edges getep.d = infand an expectation equal to the mean trans weight. The power law is fitted on cis edges only, so cis-only graphs give bit-identical O/E, andprune()keeps trans edges.- The pure-Python overlap index (used whenever
cgrangesis missing, i.e. on pip installs) is now O(log n + k) per lookup:A & Bon 100k × 100k peaks takes 24.5 s → 0.24 s. add_mcool45×,strength150× andnormalize14× faster (array work instead of per-edge loops).count_pairs/count_pairs_2dare 3–4× faster,scan_motifs_matrix3.5×, andLoci.sort/merge2–3×. The pure-Python bigWig reader is 1.7× faster on sorted peaks.from genomeblocks import Locino longer imports matplotlib, pandas or scipy: ~1.1 s → ~0.2 s.signal()usesfillna=on pybigtools ≥ 0.3, so there is no deprecation warning on every call.Loci[np.int64(i)]works.- Behaviour change:
signal(workers=n)now honoursnup to the core count; it used to be silently capped at half the cores.workers=Noneasks for half the cores.
ATAC-supported isoforms
Genes.make() / Genes.make_ucsc() take cre (peaks: a path, a Loci, or a list)
and/or bw (bigwigs), plus r (TSS half-window, defaults to promoter_r) and kw;
the new Genes.select_isoforms() does the same job on an already-parsed object. Each gene is pointed at the longest isoform whose TSS is open
in your data instead of its longest annotated isoform, which keeps long silent
isoforms (TGFBR3 and friends) from dragging a gene’s body and TSS across the locus.
Transcripts gain tss_score / tss_support / tss, genes gain canonical /
canonical_transcript / set_span(), and no transcript is dropped. See the
Genes guide.
v1.0.1 — pip-install fix
1.0.0 listed cgranges (Heng Li’s C interval index) as a dependency, but
cgranges is not published on PyPI, so pip install genomeblocks failed
with No matching distribution found for cgranges.
cgrangesis no longer a pip dependency. When it isn’t importable, genomeblocks now uses a pure-Python interval index with identical overlap results, sopip install genomeblocksworks out of the box. Installcgranges(conda, orpip install git+https://github.com/lh3/cgranges) for the fast C path on large sets.
If you installed 1.0.0 from PyPI and hit the error, upgrade:
pip install -U genomeblocks.
v1.0.0 — first stable release
genomeblocks v1.0 is the first release with a frozen public surface, a full
synthetic test suite, and complete module documentation. The public API is the
set of names re-exported from the top-level package plus the Atlas class:
from genomeblocks import (
Locus, Exon, CDS, UTR, # single intervals
Loci, # interval container + set algebra
Gene, Transcript, Genes, # gene models / GTF parsing
Architecture, # chromatin-contact graph
Atlas, # GIGGLE-style enrichment
signal, tmm, compare_heatmap, # bigWig signal
scan_motifs, make_genome, # motif scanning
browser, # region viewer
coverage, # BAM per-base coverage
)
Public surface
The building blocks and their entry points:
| Block | Build from | Key methods |
|---|---|---|
Loci |
.make(bed), .tile, .from_frame |
& \| - ^, slop, sort, merge, nearest, signal, enrich, liftover |
Genes |
.make(gtf), .make_ucsc |
annotations, nearest_genes |
Architecture |
.make(bedpe) |
add_mcool, normalize, annotate, strength, prime_hubs |
Atlas |
.make(beds) |
search, bootstrap, save/load |
signal |
loci.signal(bigwigs) |
tmm, plot_heatmap, compare_heatmap |
browser |
browser(region, tracks) |
mixed bigWig / BAM / bed / bedpe / gene tracks |
Correctness fixes since the pre-1.0 code
Locusordering across chromosomes.>and>=returned the less-than result across chromosomes, soa > bandb > acould disagree with<. Cross-chromosome comparisons are now consistent. Within-chromosome ordering (and thereforeLoci.sort/merge) was already correct.Locusis now hashable (by its UID), so aLocuscan be used in asetor as adictkey — matching its value-like__eq__.Architecture.copy()/subgraph()preserve property types. Types were inferred from a sampled Python value, but graph-tool returns booleans as ints, soboolvertex/edge properties were silently copied asdouble. Property maps now keep their exact graph-tool value type (and empty graphs keep their properties too).Genes.makeno longer crashes on a genericUTRfeature without a CDS. A non-coding transcript (or aUTRline preceding itsCDS) used to raiseIndexError; the 5′/3′ inference now falls back safely.- Signal memory guard checks RAM, not disk. The
signal()size check now compares the requested cube against available physical memory (it previously measured free space on/), and raises a clearerMemoryError.
browser import is now unambiguous
The region-viewer drawing code moved from genomeblocks/browser.py to
genomeblocks/browserview.py, so the public name browser is always the
callable — the old 'module' object is not callable footgun (where a stray
import genomeblocks.browser shadowed the function) is gone:
from genomeblocks import browser # the function
from genomeblocks.browserview import browser # equivalent
Testing
The suite is fully synthetic — no external genome / bigWig / ChIP-Atlas data —
and covers both unit helpers and end-to-end pipelines (a real bigWig written
in-test and read back through both signal backends, motif scanning on a planted
PWM, Atlas enrichment, a Loci → Genes → Architecture pipeline, and a
headless browser() render):
pip install -e ".[test]"
pytest
graph-tool must be importable for the Architecture tests (it is the one
conda-only dependency).
Upgrade notes (1.0)
There is no prior stable release to migrate from — v1.0 is the baseline. If you
were tracking main before the 1.0 tag, note that several exploratory
Architecture methods (make_spread, make_clique, cluster, focus, …),
the Tags layer, and a handful of Genes helpers were removed in the run-up to
1.0. Use the public surface in the table above.