Output columns, stage by stage
The probe-design workflow writes one parquet file per target per stage. Those files carry up to 44 columns, seven of which are DNA sequences, and the column names are terse. This page says what every one of them means.
Parquet files are not human-readable in a text editor. To look at one, use
Python with polars inside your activated environment:
import polars as pl
df = pl.read_parquet("output/MYGENE_crawled.parquet")
print(df.schema) # column names and types
print(df.head())
Terms in bold are defined in Glossary; the assay itself is explained in What SOLAR is and what these probes do.
Read this first: seq does not mean the same thing twice
seq is the column people reach for first, and it is the single most
confusing thing about these files. It changes meaning at every stage.
File |
What |
Typical length |
|---|---|---|
|
One half of a candidate window — either the padlock's binding arm or the splint's, one per row |
18–29 nt |
|
The whole binding window for the pair, in target orientation |
86–110 nt — twice the window (see below) |
|
The encoded payload: the three readout sequences plus the padlock's binding arm |
82–91 nt |
Three consequences:
In
_crawled, one probe pair occupies two rows — one named..._splint, one named..._padlock. From_screenedonward, a pair occupies one row.In
_screened,seqis currently the full window written twice, once contributed by each member of the pair, so its length is exactly double the real window length. The window itself is the first half of the string. This is a quirk of how the pair is reassembled, not extra sequence. If you want the window, takeseq[:len(seq)//2], or usepos_start/pos_end.In
_final, the old_screenedvalue is preserved under the nameseqori, andseqis the new encoded sequence. If you are comparing stages, compare_final.seqoriagainst_screened.seq.
The sequences you would actually hand to a synthesis vendor are in neither
place: they are produced later, by the assembly step, and written to
generated/<panel>_final.txt.
And the second surprise: paired columns are lists
From _screened onward, one row is one probe pair. Every per-half value is
therefore collected into a two-element list:
priority : [1, 6]
oks : [6, 3]
tm : [43.2, 39.8]
The two entries are the pair's two halves — but the order is not
guaranteed to be splint-then-padlock, and in practice it varies row to row.
Treat these as an unordered pair. In particular, to reduce them to one number
per pair use an explicit aggregate (.list.min(), .list.max(),
.list.mean()), never .list.get(0). The package itself does exactly this,
sorting probes by priority.list.max() and hp.list.min().
Only name, gene, pad_start, splint, padlock, seq (and in _final,
code1/code2/code3 and seqori) are plain scalars.
<target>_crawled.parquet — the candidate pool
One row per candidate half that survived the initial alignment screen. Expect thousands of rows for a normal-length transcript; they overlap heavily, because the crawler emits a candidate from nearly every starting position. Nothing here has been selected yet.
Column |
Type |
Meaning |
|---|---|---|
|
String |
Full probe-half identifier: |
|
String |
This half's binding sequence, in target orientation (not yet reverse-complemented). 18–29 nt. |
|
String |
The complete window both halves came from, before splitting. 43–55 nt. |
|
String |
Gene name parsed from |
|
String |
The transcript this probe was designed against, parsed from |
|
UInt32 |
0-based start of the full window within the transcript sequence. |
|
UInt32 |
0-based end of the full window, inclusive. |
|
UInt32 |
|
|
Int16 |
Offset within |
|
String |
The transcript this row's alignment landed on. In |
|
UInt32 |
Line number of the underlying SAM record. Internal bookkeeping. |
|
see below |
Standard alignment fields carried through from |
|
Int64 |
Number of bases that matched the reference in this alignment (the sum of the run lengths in |
|
UInt8 |
Longest uninterrupted run of matching bases in this alignment. |
|
UInt8 |
The worst (largest) |
|
Float32 |
The worst-case off-target melting temperature, in °C. See Off-target columns. |
|
String |
For human/mouse datasets, the ID of an allow-listed extra transcript (typically a pseudogene you accepted) that this probe also binds. Empty string for custom-species datasets, which do not run pseudogene triage. |
|
Float64 |
Fraction of |
|
Float64 |
Predicted melting temperature of |
|
Float64 |
Hairpin score: predicted melting temperature in °C of the most stable self-structure |
|
Boolean |
Sequence-quality flag. The name is misleading — see below. |
|
Boolean |
Sequence-quality flag. The name is misleading — see below. |
|
Boolean |
Sequence-quality flag. |
|
Boolean |
Sequence-quality flag. The name is misleading — see below. |
|
Boolean |
Sequence-quality flag. |
|
Boolean |
Sequence-quality flag. |
|
UInt32 |
How many of the six |
<target>_screened_ol*.parquet — the selected probe pairs
One row per selected probe pair. Expect tens of rows, tiled along the transcript with a small gap between neighbours. This is the file panel QC counts, and the one the construct stage reads.
It carries every _crawled column except seq_full (which became seq),
with the per-half values collected into lists, plus these:
Column |
Type |
Meaning |
|---|---|---|
|
String |
The pair's identifier: |
|
String |
The splint's target-binding half, in target orientation. 18–29 nt. |
|
String |
The padlock's target-binding half, in target orientation. 18–27 nt. |
|
String |
The full binding window, written twice (see the warning above). |
|
List(UInt8) |
Which tier of the filter cascade this half satisfied: |
|
List(UInt32) |
Row number used internally by the tiling algorithm. Not meaningful outside it. |
|
String, Int16 |
Scalars, as in |
Everything else — id, flag, transcript, pos, cigar, aln_score,
aln_score_best, n_ambiguous, n_mismatches, n_opens, n_extensions,
edit_distance, mismatched_reference, transcript_ori, pos_start,
pos_end, length, match, match_consec, maps_to_pseudo,
max_tm_offtarget, match_consec_all, all six ok_* flags, gc_content,
tm, hp, oks — is the _crawled column wrapped in List(...).
<target>_final_*.parquet — the encoded constructs
One row per constructed probe pair, after the gene's three readout sequences
have been stitched onto the padlock. Slightly fewer rows than _screened:
the construct step tries four different spacer choices per probe, and drops
the probe entirely if all four would produce a run of five identical bases
(AAAAA, TTTTT, CCCCC, GGGGG) or a BamHI site in the joined sequence.
Column |
Type |
Meaning |
|---|---|---|
|
String |
Same pair identifier as in |
|
String |
The encoded payload: the three readout sequences (lowercase, reverse-complemented) joined by two-nucleotide spacers, followed by the padlock's binding half (uppercase). 82–91 nt. |
|
Int64 |
First readout ID written onto this probe. |
|
Int64 |
Second readout ID. |
|
Int64 |
Third readout ID. |
|
String |
The |
|
String / Int16 |
Unchanged from |
All other columns are the _screened list columns, unchanged.
About code1/code2/code3: together they are the gene's codebook entry.
Every probe for a gene gets all three of its bits — it is not the case that
some probes carry one bit and others carry another. Their order varies from
row to row, because the construct step cycles through the permutations of the
triple looking for one that does not create a forbidden motif. So
{code1, code2, code3} as a set is the meaningful thing, and it will match the
bits token in the filename. Readout IDs run from 1 to 49.
The alignment columns
These seven columns come straight from bowtie2's SAM output and are not
specific to this pipeline. If you have never read a SAM file, this is all you
need:
flagA bitfield describing the alignment. In these files you will see
0(aligned to the forward strand, best alignment for this probe),16(aligned to the reverse strand),256(a secondary alignment — the same probe also aligned somewhere else, and this row is one of the other places), and272(secondary and reverse strand).posThe 1-based leftmost coordinate of the alignment on the transcript named in
transcript. Do not confuse it withpos_start, which is a coordinate on the transcript the probe was designed against.cigarA compact description of the alignment shape.
23M= 23 aligned bases.1S21M4S= 1 base soft-clipped at the front, 21 aligned, 4 clipped at the end — a partial match, which is what most off-target hits look like.aln_scorebowtie2's alignment score for this alignment (
AS:iin SAM). Higher is a better match.aln_score_bestMisleading name. This is bowtie2's
XS:ifield: the score of the best alignment found for this probe other than the one on this row. It is present only when the probe aligned more than once. When it equalsaln_score, the probe has at least two equally good placements — usually sibling isoforms of the same gene.n_mismatches,n_ambiguous,n_opens,n_extensions,edit_distanceRespectively
XM:i(mismatched bases),XN:i(ambiguous reference bases overlapped),XO:i(gaps opened),XG:i(gap extensions), andNM:i(total edit distance to the reference).mismatched_referenceThe SAM
MD:Zfield, describing the alignment from the reference's point of view: numbers are runs of matching bases, letters are the reference base at a mismatch.23means 23 matches in a row;10A12means 10 matches, a mismatch where the reference had an A, then 12 more matches. Thematchandmatch_conseccolumns are computed directly from this string —matchis the sum of the numbers,match_consecis the largest of them.
Off-target columns
"Off-target" is not recorded as a yes/no flag. Every candidate is aligned against the entire transcriptome, alignments to acceptable transcripts (the target itself plus its sibling isoforms plus anything you explicitly allowed) are set aside, and the remaining hits are summarised into two numbers.
match_consec_allThe longest uninterrupted match this probe half achieved against any unacceptable transcript.
0means nothing off-target matched at all.max_tm_offtargetThe important one. For every off-target alignment with a run of more than 16 consecutive matches, the pipeline finds the longest perfectly matched stretch of at least 15 bases and computes its melting temperature; this column holds the highest such value across all off-target hits, in °C.
0.0means no off-target hit was substantial enough to be worth scoring. This is a thermodynamic measure, not a count: a 25-base off-target match at high GC is far more dangerous than a 25-base match at low GC, and this column reflects that. The default screening threshold is 20 °C.maps_to_pseudoA separate, human/mouse-only channel for hits you decided to tolerate. It holds the ID of an allow-listed transcript (typically a pseudogene or close paralog) that this probe also binds. The two strictest filter tiers require it to be empty, so a probe with a pseudogene hit can still be used, but only once the stricter tiers have been exhausted.
The ok_* quality flags, precisely
Six boolean checks are applied to each half's seq. oks counts how many
passed. Three of the names do not describe what they test. Those names are
scheduled to be changed but have not been changed yet, so read the table, not
the name.
Flag |
What it actually tests |
Name accurate? |
|---|---|---|
|
|
No. The name says |
|
|
No. The name says |
|
|
Partly — again G, not C. |
|
|
No. The name says |
|
|
Yes. |
|
|
Yes. |
Note that ok_homopolymer implies both ok_quad_c and ok_quad_a, so the
flags are not independent. oks therefore ranges 0–6, and 6 means a clean
sequence on every count.
And once more, because it catches everyone: hp is the hairpin score, not
a homopolymer score. Homopolymers are ok_homopolymer.
The filter cascade
priority records which tier of a seven-step cascade a probe half satisfied.
Tier 1 is strictest; each subsequent tier relaxes the requirements. Screening
first tries to tile the transcript using only tier-1 probes; if that does not
yield enough, it retries allowing tiers 1–2, then 1–3, and so on.
|
Requirements |
|---|---|
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
A candidate that satisfies none of the seven is discarded entirely, so
priority is never 0 in a written file. A panel dominated by priority 6–7
probes is a warning sign: it means the strict tiers could not fill the
transcript, and you should look at the gene's *.stats.json before trusting
it.
Decoding the filenames
Filenames encode the parameters that produced them, which is how you can tell two runs apart in the same directory.
Och.958.1_crawled.parquet
Och.958.1_screened_ol-2_BamHIKpnI.parquet
Och.958.1_final_BamHIKpnI_4,5,6.parquet
<target>The transcript the probes were designed against. For human/mouse reference datasets this is the Ensembl transcript name; for custom-species datasets it is the transcript ID exactly as it appears in your annotation — which is why these names can contain dots and underscores.
_crawledThe raw candidate pool.
_screened_ol<overlap>The selected, tiled probe pairs.
<overlap>is the spacing parameter, and it is negative by default (-2)._<enzymes>The restriction enzymes candidates were screened against, concatenated with no separator between them.
BamHIKpnImeans BamHI and KpnI, which is the default pair. Absent if screening ran without enzyme filtering._final_<enzymes>_<bits>The encoded constructs.
<bits>is the gene's three readout IDs, sorted ascending and comma-separated —4,5,6. If you regenerate the codebook and the gene's bits change, you get a new file rather than an overwritten one, so stale files with old bit triples can accumulate. Delete them, or the assembly step may pick up the wrong one.
The ol value is a gap, not an overlap
This one is genuinely counterintuitive, so read it twice.
The parameter is named "overlap", and larger positive values do mean more
overlap between neighbouring probes. But the default is -2, and a negative
overlap means a gap: adjacent selected probe windows must be separated by at
least that many nucleotides of untouched transcript. ol-2 therefore means
"leave at least a 2-nucleotide gap between neighbouring probes", not
"let neighbouring probes overlap by 2".
This is confirmed by the output files themselves. In three test panels
produced with ol-2, the smallest gap between adjacent selected windows is 2,
3 and 2 nucleotides respectively — no pair of probes overlaps at all.
Positive ol values are only reached when a gene cannot supply enough probes
at the default spacing, at which point the screening step walks upward
(-2, then 5, 10, 15, 20) allowing progressively more overlap until it hits
the requested probe count. In the default panel configuration that escalation
is switched off, so _ol-2_ is what you will normally see.
The other files in the output directory
Not everything the pipeline writes is a probe table.
<target>_all.parquetEvery alignment of every candidate half against the whole transcriptome — the raw material the off-target columns are computed from. This is by far the largest file per gene (around 2 MB for a 16 kb transcript, versus 140 kB for
_crawled) and is kept mainly so a re-run can skip realignment. Safe to delete once a gene is finished.<target>_bowtie.parquetA three-column summary of
_all:transcript,count,name— how many candidate halves hit each transcript in the transcriptome, sorted descending. The quickest way to see whether a gene has a homologue problem: if a transcript you did not expect sits near the top, your probes are not specific.<target>_crawled.stats.jsonTranscript length, how many candidates the crawler produced, the list of transcripts treated as acceptable, the top off-target counts, and how many candidates survived the match filter.
<target>_screened_ol*.stats.jsonHow many candidates entered screening, how many survived each filter tier (
selected_1throughselected_7), and how many pairs came out. If a gene is short on probes, this file tells you at which tier it ran out.<target>_crawled.coverage.txtA plain-text ASCII picture of where along the transcript the candidates fall. Useful for spotting a transcript where everything clusters at one end.
<target>.logThe full per-gene log from the parallel design run, with timestamps. The first place to look when one gene fails and the rest succeed.
<target>_offtarget_counts.csv,<target>_acceptable_tss.csvHuman/mouse reference datasets only. The first lists significant off-target binders for interactive triage; the second lists every transcript that was treated as an acceptable binder for this target.
See File formats and conventions for the dataset-level files (dataset.json,
solar_intake.yaml, index and k-mer files) and for which kind of dataset each
command loads.