Interactive views, figure export and labelled pileup tracks - #1
Merged
Merged
Conversation
`faba qc -I` opens a full-screen view of the editing-site thresholds after the cell calls and before the site cut; Enter applies them. `faba qc-report -I` opens the same view after the sweep and prints the matching `faba qc` flags on Enter. Without a terminal on stdin and stdout the view is skipped and the --site-* values stand as given. The view shows each threshold's value, the sites it drops alone and the sites for which it is the first failing check, beside a histogram of the column it cuts. Sites passing every other threshold are drawn in front, and the bars the threshold drops use the accent colour. Axes switch between log, sqrt and linear where the column allows it; p-values default to log bins on -log10 p so the region around the usual cutoffs is resolved. To keep the view and the written fileset from disagreeing, the site thresholds now live in one table (`Criterion` in qc/sites.rs), and `SiteFilterArgs::reason` walks it in a fixed check order. The written `reason` labels are unchanged. The view keeps per-site fail bits, so a key press rechecks only the threshold that moved. `qc` now holds the per-batch cell QC files in memory and creates the output directory only once nothing can cancel, so cancelling the view leaves nothing behind. Builds on data-beans 0.6.15, which exports its terminal UI pieces behind the `tui` feature; ratatui 0.30 is added for the frame and event types.
…nel rows `faba pwm -I` shows the PWM as a sequence logo: one letter stack per position, tallest on top, in information content or frequency, with the site in the accent colour and a cursor reading out each position. `faba metagene -I` draws the 5'UTR, CDS and 3'UTR bins end to end with the region boundaries labelled, and the ncRNA track on Tab when it was profiled. Adjacent bins merge within a region to fit the terminal, or to a chosen factor; a cursor reads out region, bins, MetaPlotR coordinates and count. `faba pileup -I` browses the gene body with the matrix track and the site track on one genomic axis. Each frame re-bins the raw positions of the visible window, one bar per column, with the printed pileup's rule; the cursor pans, zooms and jumps between sites. All three open after the normal output is written and are skipped without a terminal. They build on data-beans 0.6.16, whose HistPlot takes unit-width bins, custom tick labels and real-valued bars. `pileup` also failed to match any row of current `_site` matrices, whose names end in a channel (`.../chr:pos/methylated`). Such rows now pile up the converted channel (methylated or edited) and skip the unconverted one.
data-beans 0.6.16, legume-numeric 0.8.11, legume-genomic-types 0.4.2.
A shared figure module draws each view as an SVG and writes it as a
vector PDF (svg2pdf) and a PNG (resvg), so exported figures are the
same whatever the terminal can show. In every full-screen view, `s`
asks for a file name and saves the current view: thresholds and
histogram in `qc`, the logo in `pwm`, the shown track in `metagene`,
and the visible window in `pileup`.
`qc-report` draws its panels to {prefix}.qc_report.pdf/.png and loses
`-I`, which duplicated `qc -I`. `qc -I` gains `p`, which prints the
matching `faba qc` flags and writes nothing.
`--track LABEL=PATTERN` (repeatable) groups the input files into labelled tracks: each file joins the first track whose pattern (`*`, `?`) matches its path. Tracks are printed, written to the TSV and browsed separately; without `--track` every file is pooled into one `matrix` track as before. `pileup -I` with no `--genes`/`--regions` reads the inputs' row names into a list of genes with their site counts and extents, filtered as you type. Enter opens the gene in the browser; `g` there returns to the list, which keeps its filter. The matrix reader now reports how many rows matched instead of failing, so a track whose files lack the gene is empty rather than an error; figure mode still fails when nothing matches.
In kitty, iTerm2, WezTerm, Ghostty and other terminals with an image protocol (ratatui-image), each view's plot area is drawn from the same SVG the export writes, rasterised to the area's pixel size and rebuilt only when the view or the area changes. `i` switches between image and text. Terminals without an image protocol keep the text plots. The terminal is only queried when its environment names one of those terminals (or FABA_TUI_IMAGES=1): a terminal that never answers the query would lose the next keystroke to the query's reader. FABA_TUI_IMAGES=0 turns images off. In text, the pwm logo draws letters with room for it from 5x7 bitmaps instead of repeating the glyph. In image mode, metagene fits its bins to the pixel width rather than the column width.
In the browser, `/` takes a gene name, Ensembl ID, `chr:start-end` or `chr:pos` (commas allowed). A locus inside the current view moves the window there (a position keeps the zoom); a locus elsewhere loads that region; a gene opens when one matches and shows the gene list filtered by the query when several do. What cannot be found is said on the footer. The gene list takes a typed locus the same way. Search works from `-q`/`--regions` too: the browser, the gene list and search now share one loop, which reads the gene list from the inputs' row names the first time it is needed.
A green, C blue, G amber, T red, as sequence logos usually are, in the exported figure, the on-screen image and the text letters. The site is marked by an accent tick and label under position 0.
The rows read "min edit ratio" and "max edit ratio", and each histogram says what its tail means: a low ratio is weak editing, a high one looks like a genomic variant.
As in the browser: it clears the filter, and the footer now says the list takes a gene name or a chr:start-end.
The table's counts get two-line headers and a legend under them: "drops alone" (dropped if this were the only threshold), the new "drops only" (sites failing this threshold and passing all others, i.e. what turning it off keeps), and "reason column" (its count in the dropped table, which names the first check a site fails). The saved figure carries the same columns and legend.
- figure::Controls owns the save prompt, image support, the `i` toggle, the header tag and the help keys that four views repeated. Images are rebuilt only when a key changes the view, not while a name or search is typed, and the terminal picker is no longer cloned each frame. - One LineInput for the save and search prompts, one rasteriser for PNG export and on-screen images, figure::svg for a view's plot, one XML escape for every SVG writer, and term::when_terminal for `-I`. - pileup: Selector has an explicit exact mode; a lazy Catalog lets search load the gene list itself, so the loop no longer pre-parses queries; the gene list is read one file at a time; `g` always returns to the list. - metagene fits bins to the text width in both modes, so a saved figure does not depend on the display mode. - qc: one threshold text, one pointer and dropped-bin rule for screen and figure, per-bin tick labels computed with the bins, and the picker's outcome is `Picked` directly. - qc-report's ASCII and PDF panels share one grouping; pwm has one stacking order.
In the browser, matrix tracks now draw the converted channel (methylated or edited reads summed over cells) in front of both channels summed, so the unconverted reads show as the grey above; the readout gives both, e.g. `wt 152/180`. The unconverted rows are read for the browser only: the printed pileup and TSV stay converted-only, and `nnz` is not stacked, since cells would be counted twice. The site-table track can switch with `c` between methylated / unmethylated reads (the default, from the `converted` and `coverage` columns), the same for the control arm, sites per bar, and -log10 p. Saved figures draw the same stacking.
When the first two tracks carry totals, the browser's top row compares their converted fractions per bar: the difference in percentage points or the log2 fold (`c` switches), drawn as signed bars around zero, accent where the first track is higher. Bars without reads in either track show nothing; the readout leads with the value at the cursor. Saved figures carry the same row. The site-table track keeps its read-count views and -log10 p, now on `v`; the "sites" view, which only counted sites per bar, is gone.
`pileup -I --gtf FILE` reads the annotation once and draws a genes row under the tracks: every gene overlapping the window, exons as boxes on an intron line with strand arrows and the symbol, overlapping genes in separate lanes. Saved figures draw the same models with the figure mode's drawer. Without `--interactive`, `--gtf` still makes the figure. Labels follow the modality: m6A reads are methylated / unmethylated, A-to-I reads converted / unconverted, in the matrix tracks, the contrast and the site-table views. The site-table track is now called "total" (reads pooled in the site table), switched with `t`.
The browser no longer draws the site table's pooled total: with per-track read counts it adds nothing. The site table still bounds the gene and feeds the printed pileup and TSV. Tracks now carry a single series with an optional total, so the switchable views and `t` go. Gene models are slimmer: half-height exons and tighter lanes in figures; in text a lane is one line, exons a heavy line on the intron line, the symbol beside the gene. When the view is a gene, only that gene's model is drawn; a searched locus shows the genes in view. A matrix with no unconverted rows is drawn unstacked rather than as a total equal to its converted count.
`--depth FILE...` adds a depth row to the browser from `_depth`
matrices: the bins overlapping the loaded region, each bin's reads
summed over cells and files, drawn as a step across the columns it
covers. It works for a searched locus as well as a gene, and saved
figures carry it.
With `--gtf`, the annotation is read on a thread, so the browser opens
at once; until it is ready the footer says so and the gene row appears
from the next view.
A test ties pileup to the row names the producers write today
(`{gene}/{modality}/{chr}:{pos}/{channel}` for sites, `{chr}:{start}-{end}`
for depth), so a naming change fails the test instead of emptying the
pileup. Single-series readouts use compact numbers.
Gene models in figures and images get 8-unit exons (from 5) on 18-unit lanes. Gene models read in the background now land in the view already open: it keeps a handle on the annotation and draws its gene from the next key after the models arrive, rather than only in the next view.
A log line printed while a view owns the terminal scrolls it and tears the picture: toggling the pileup contrast in image mode did this, as the SVG renderer warned about every zero-height bar. Views now run with logging paused (restored on exit), and the canvas no longer writes rectangles of zero or negative size, which is what a bin with no difference produced.
The browser draws every read track (not depth or the contrast) against one y-axis top, the tallest bar across them, total included, so `wt` and `mut` read on the same scale; saved figures and images do too. Needs data-beans 0.6.17 for HistPlot's y_max.
Rows carry their contrast measure, gene models are filtered to the view's chromosome and extent, a failed annotation read shows on the footer, and column ranges come from one BinEdges helper.
When the first two tracks show the same measure they share one row, the first growing up and the second down on one scale, as a Miami plot; m splits them back into separate rows. The difference row is drawn by the same mirrored plot.
- Gene models are chosen by the matched gene keys (by symbol when the annotation versions its ids), not parsed back out of the title. - Views run on run_screen directly: log records are held while a view is up, and the browser redraws when the models arrive, no key needed. - The text mirror and difference rows use data-beans' MirrorPlot. - Site rows split with parse_feature_row and channels, so a gene symbol may hold a slash. - A track holds reads or depth bins as an enum; the compared pair is stored once; each frame bins every track once. - Gene models draw through Canvas in the figure palette, with smaller strand chevrons. - The GTF reader keeps only gene and exon lines.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Full-screen views for the stat and QC commands, a shared figure export, and a few fixes they exposed.
Views
faba qc -I: the editing-site thresholds combined, before the site cut.pprints the matchingfaba qcflags and writes nothing.faba pwm -I: a sequence logo in information content or frequency, with the site in the accent colour and a cursor reading out each position.faba metagene -I: 5'UTR, CDS and 3'UTR end to end, with the boundaries labelled.-/+).faba pileup -I: labelled tracks on one genomic axis.--genes/--regions, it opens a gene list filtered as you type;greturns to the list.All views are skipped without a terminal on stdin and stdout.
Figures
sasks for a file name and saves the current view as a vector PDF (svg2pdf) and a PNG (resvg), drawn from one SVG.faba qc-reportdraws its panels to{prefix}.qc_report.pdf/.pngand no longer has-I(it duplicatedqc -I).iswitches to text.FABA_TUI_IMAGES=1, because a terminal that never answers the query would lose the next keystroke.FABA_TUI_IMAGES=0turns images off.pileup
--track LABEL=PATTERN(repeatable) groups the inputs into labelled tracks: each file joins the first track whose pattern (*,?) matches its path.--track, everything is pooled into onematrixtrack as before.pileupmatched no row of current_sitematrices, whose names end in a channel (.../chr:pos/methylated). Rows are now split with data-beans'parse_feature_row, so a gene symbol may also hold/.-I):/searches a gene,chr:start-endorchr:pos, from the view or from the gene list.msplits them.c).--gtfadds a gene model row (exons on an intron line with strand chevrons): the opened genes, or every gene in view for a searched locus. The annotation is read in the background, keeping only gene and exon lines, and the view redraws when it arrives.--depthadds a read-depth row.Consistency with
qcCriterioninsrc/qc/sites.rs).SiteFilterArgs::reasonwalks that table in a fixed check order, and the view counts with the same checks.reasonlabels are unchanged.Dependencies
aux,tui): the shared terminal UI pieces, histograms of categories and real values on a shared scale, the mirrored plot, redraws without a key, and held logs.Testing
src/**/tests/*.rs) drive the keys and render into ratatui'sTestBackend. They cover:reason();cargo +1.94 fmt --all --checkandcargo +1.94 clippy --all-targets -- -D warningsare clean.qcwrote exactly the counts on screen, and cancelling wrote nothing;