Windows-GPU-UNLOCK
Windows GPU UNLOCK: RTX3090 UEFI port, Korean no-reset Windows boot guide, verified source and benchmark evidence
翻译准备中
Universal & Agentic Probe Design Tool
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗U-Probe is a universal and agentic probe design platform tailored for imaging-based spatial-omics. It overcomes the architectural limitations of existing tools and lowers the expertise barrier by combining an innovative declarative configuration system with LLM-based AI agents.
Whether you are designing standard probes for established protocols or developing entirely novel architectures, U-Probe provides a comprehensive, automated workflow from target sequence extraction to rigorous thermodynamic filtering.
uprobe/skills/u-probe/) lets AI coding agents such as Claude Code or Codex design probes with U-Probe, and the web server offers conversational design through hierarchical LLM-based AI agents (via PantheonOS, an optional extra). Users can describe experimental goals in plain language or provide scRNA-seq data, and the agents will autonomously construct configurations, select parameters, and execute the design pipeline.Requires Python 3.10+. Use Linux or WSL2 for backend development and bioinformatics tools.
pip install uprobe
git clone https://github.com/u-science-lab/U-Probe.git
cd U-Probe
pip install -e .
git clone https://github.com/u-science-lab/U-Probe.git
cd U-Probe
conda env create -f environments.yaml
conda activate uprobe
pip install -e .
The conda environment also installs the command-line tools U-Probe calls (Bowtie2, Bowtie 1, BLAST+, MMseqs2, Jellyfish). With a pip install, install the ones you use from bioconda, e.g. conda install -c bioconda bowtie2 bowtie jellyfish. Bowtie 1 (bioconda package bowtie) runs the exhaustive off-target search, which short probes need: the smFISH, seqFISH+, MERFISH and HybISS templates use it, and so does the generated off-target attribute of a protocol without attributes when the genome enables it. uprobe genome fetch adds bowtie to a new genome's align_index when Bowtie 1 is installed.
U-Probe ships an Agent Skill in uprobe/skills/u-probe/. Its SKILL.md gives a coding agent the design workflow (clarify the request, choose the technology and template, fetch the genome, edit, validate, run, verify, deliver) and the rules behind it, and sends the agent to reference notes on each technology, on U-Probe's YAML and on probe-design concepts when it needs them; scripts/check_probes.py checks a result table before delivery. Install U-Probe and the aligners, then copy the skill to where your agent looks for skills:
pip install uprobe
conda install -c bioconda bowtie bowtie2 jellyfish
uprobe skill install --target codex # ./.agents/skills/u-probe (run it at the repository root)
uprobe skill install --target claude --user # ~/.claude/skills/u-probe
uprobe skill path # the copy inside the installed package
| Agent | Project (default, working directory) | --user | Source |
|---|---|---|---|
| Codex | .agents/skills/u-probe | ~/.agents/skills/u-probe | Codex docs: Agent Skills |
| Claude Code | .claude/skills/u-probe | ~/.claude/skills/u-probe | Claude Code docs: Skills |
Without --target the skill is installed for both agents; --target DIR installs it as DIR/u-probe for any other agent that reads Agent Skills. After upgrading U-Probe, run the install again with --force so the skill matches the CLI. Then describe the experiment to the agent in plain language, e.g. "Design smFISH probes against human GAPDH mRNA for HeLa cells".
U-Probe provides three main entry points: Command Line Interface (CLI), Python API, and the HTTP/Web UI.
For source installs, install the package and runtime dependencies before running examples:
pip install -e .
Most workflow commands require two YAML files:
genomes.yaml: genome paths such as FASTA and GTF.protocol.yaml: probe design parameters.The CLI is perfect for running tasks quickly in the terminal or shell scripts. A typical session, also the path for scripts and AI agents, fetches a reference genome, starts from a published template, checks the protocol and runs it; each step is described below.
uprobe genome fetch --species mus_musculus --release latest --genomes genomes.yaml
uprobe templates show padlock_iss -o protocol.yaml # then set genome, targets and encoding
uprobe validate -p protocol.yaml -g genomes.yaml --check-targets
uprobe run -p protocol.yaml -g genomes.yaml -o ./results --json
The recommended way to use U-Probe with an AI agent such as Claude Code or Codex is the Agent Skill under uprobe/skills/u-probe/; install it with uprobe skill install (see Use U-Probe with an AI agent). U-Probe no longer ships a terminal agent of its own: the uprobe agent command has been removed. The web server keeps its chat agent as an optional extra (see below).
Run the FastAPI backend in WSL/Linux and the separate React frontend in Windows. First check config.ini: use local Linux paths under [Paths] and frontend_url = http://localhost:5173 under [Server].
cd /path/to/U-Probe
conda activate uprobe # or your existing Python 3.10+ environment
uprobe server --env development --host 127.0.0.1 --port 8005 --workers 1
Development mode reloads Python changes automatically. Alternatively, run APP_ENV=development PORT=8005 python -m uprobe.http.server from the project root. Running without overrides uses config.ini, which may select production mode.
The web chat (the /agent endpoints) is the optional web-agent extra: install it with pip install 'uprobe[web-agent]', or pip install -e '.[web-agent]' from source. Without it the server starts and those endpoints answer 503; deployments that serve the chat, such as app.u-probe.org, must install it.
In a separate Windows PowerShell terminal:
cd /path/to/probe-web-ui
pnpm install # first run or after dependency changes
pnpm dev --host 127.0.0.1 --port 5173
{"message":"Hello, World!"}.The frontend proxies /api to backend port 8005. No separate database service is needed; users and tasks are stored locally. Create a test account through POST /auth/register in the API docs; this endpoint needs no email verification. Full probe workflows also require FASTA/GTF data and the relevant bioinformatics tools.
Download an Ensembl genome FASTA and GTF, verify them against Ensembl's CHECKSUMS, and register them in genomes.yaml:
uprobe genome fetch --species homo_sapiens --release latest --out ./genomes --genomes genomes.yaml
uprobe genome fetch --species arabidopsis_thaliana --division plants --build-index -t 10
uprobe genome search mouse # find Ensembl species names
uprobe genome list -g genomes.yaml # entries, file and index status (--json for scripts)
--species takes an Ensembl name (homo_sapiens), display name (human) or taxonomy id (9606). --release latest is resolved to a number, which is recorded in the entry.plants, fungi, metazoa and protists (--division; they have their own release numbers). Ensembl Bacteria is not supported.<out>/<name>/; the entry name defaults to <species>_<assembly>_e<release> (_eg<release> for Ensembl Genomes).--soft-masked fetches the soft-masked FASTA instead (Ensembl dna_sm: repeats in lowercase; the name gets the suffix _sm). Every template can use it, since the aligners and Jellyfish ignore case; the Oligopaint template needs it, because its extracts.target_region.masked: drop skips the genome windows with a soft-masked or N base, as OligoMiner mines a repeat-masked genome.--dry-run prints the resolved URLs without downloading; --mirror replaces the Ensembl FTP base URL.align_index: [bowtie2], plus bowtie when Bowtie 1 (bowtie-build) is installed, for the exhaustive off-target search (the log says so when it is not), and jellyfish: true when Jellyfish is installed. --align-index and --jellyfish/--no-jellyfish set them; re-fetching keeps the settings of an existing entry.--build-index builds every index of the entry's align_index, as uprobe build-index --all does.When you have transcript sequences and a table of transcripts instead of a genome and its GTF, register them directly:
uprobe genome add human_tx --transcriptome transcripts.fa --records transcripts.tsv --align-index bowtie
--transcriptome is a plain-text FASTA with one record per transcript (the header up to the first space is the transcript id, matched with or without a .N version). --records is a TSV (CSV when the name contains .csv) with a transcript id column (transcript_id, transcript, enst, ...), a gene name column (gene_name, gene, symbol, ...), and optionally a gene id (gene_id, ensg; default: the gene name), tags and CDS start/end within the transcript (cds_start, cds_end). MANE Select and Ensembl canonical come from a tag/tags column (comma-separated GTF tags), a mane_select/mane/mane_status column (1/true/yes, or a value containing Select) and an ensembl_canonical/canonical column (1/true/yes); name other columns with --transcript-column, --gene-column, --gene-id-column, --tag-column, --mane-column and --canonical-column.<out>/<name>/annotation.gtf; transcript, exon and, with CDS columns, CDS lines carrying gene_name, gene_id, transcript_id and the tags), so source: mRNA (and exon, CDS) with transcripts: mane or canonical and gene-name targets work as with a genome. <out>/<name>/transcript.fa holds the transcripts named <gene_id>_<transcript_id>, the names the off-target search reads (a _ in a gene id is written as -), so every transcript of the table is in the off-target index. FASTA records missing from the table are left out (--unlisted include keeps each as a gene of its own); the command logs how many.fasta is a link in <out>/<name>/ to your FASTA, and its index_dir is that directory (or --index-dir), so nothing is written next to your files. Only the indexes a protocol searches are built (--align-index bowtie is enough for the exhaustive off-target search of windows up to 30 nt; no genome-scale index is built). reads the inputs and prints the entry only.Any entry can also move or reuse its indexes with two optional keys:
human_tx:
fasta: /data/transcripts.fa
gtf: /data/annotation.gtf
align_index: [bowtie]
index_dir: /fast/disk/human_tx # <aligner>_genome/, <aligner>_transcript/, jf_genome/ and transcript.fa live here, not next to the FASTA
indexes: # an index that already exists: the prefix given to bowtie / bowtie2
bowtie_transcript: /data/bowtie/transcripts # used as it is, never built; `uprobe validate` errors when it is incomplete
uprobe genome add ... --existing-index bowtie_transcript=/data/bowtie/transcripts writes the second key. An index listed under indexes must name the transcriptome (transcript) or genome (genome) index of an aligner in align_index; with the default layout the prefix is <index_dir>/<aligner>_<kind>/<FASTA stem>.
A transcriptome index given under indexes must be built from records named <gene_id>_<transcript_id>, with the gene ids of the entry's GTF: the transcript.fa U-Probe writes (genome add writes it to index_dir; for a genome it is extracted from the GTF). mapped_genes reads the gene of each hit from the record name, up to the first _. An index of an ordinary transcript FASTA, named by transcript ids, gives wrong counts: the target's own isoforms (ENST00000229239.10, ENST00000396861.5) count as off-target genes, and RefSeq records (NM_002046.7, NM_001256799.3) all count as one gene, NM. genome add --existing-index, uprobe validate and the run list the records of such an index (bowtie-inspect -n or bowtie2-inspect -n) and stop with an error, naming examples, when a record's gene is not a gene of the GTF; when the inspect program is not on PATH the names are not checked and validate warns. Build the index from that transcript.fa (e.g. bowtie-build <index_dir>/transcript.fa <prefix>), or leave the key out and let U-Probe build it.
U-Probe ships complete protocol templates for published probe technologies: smFISH (Stellaris-style), MERFISH, seqFISH+, HCR v3.0, STARmap, padlock ISS (HybISS), MiP-seq, Oligopaint/OligoMiner and the double-hybridization RCA design. Each template declares the source protocol's hybridization conditions; where a paper gives a Tm window together with the conditions it was computed under (MERFISH, Oligopaint, HybISS), the Tm attribute is computed under those conditions with the design code's own formula (tm_method: merfish_design, oligominer, multi_padlock_design) and the window is applied as a filter; smFISH, HCR v3 and MiP-seq have a Tm floor and STARmap a Tm window taken from their published probe sets under the template's conditions (meta.parameter_sources). A meta block cites the source papers, maps every parameter to its source and lists what you must replace (genome, targets, barcodes/readouts). Every template passes uprobe validate. Every RNA template designs on the reference transcript (transcripts: mane: MANE Select, the Ensembl canonical transcript without it) and takes the region its source designed on (meta.parameter_sources quotes the evidence): the whole mRNA for smFISH, MERFISH, HCR v3, HybISS and MiP-seq, whose published probes also lie in UTRs, and the CDS for seqFISH+ and STARmap, which used the coding region, and for the double-hybridization RCA design. Raj et al. 2008 tiled the coding region of each smFISH target; the smFISH template takes the whole mRNA, as the Stellaris designer suggests for expanding a set, so that genes sharing coding sequence with paralogs or pseudogenes still reach 48 probes (source: CDS for the coding region alone). Templates whose target windows are 30 nt or shorter (smFISH, seqFISH+, MERFISH, HybISS) count off-target genes with the exhaustive Bowtie 1 search, and so do the templates of units of several oligos (HCR v3, STARmap, MiP-seq), per oligo (oligos), so their genome needs bowtie in align_index; the smFISH template keeps only probes without an off-target gene, exempts copies of the target (exempt_copies, as the MERFISH and seqFISH+ templates do), recommends (commented out) an expression table, by transcript when you have one, with min_fraction_of_target, and offers off_target_pool as a commented-out option for genes with many paralogs; see Off-target search.
uprobe templates list
uprobe templates show hcr_v3 -o protocol.yaml
Check a protocol (and optionally the genome config) before running it. Every problem is reported with its location in the YAML and a suggested fix; the exit status is 1 when there are errors.
uprobe validate -p protocol.yaml
uprobe validate -p protocol.yaml -g genomes.yaml --check-targets --json
Probe expressions are checked with the grammar of the safe evaluator that uprobe run uses, hybridization blocks with the same rules as the run, and the probes are then built on synthetic target regions, so expressions that would fail at run time are reported too. -g adds genome checks (files, aligner indexes required by the attributes, installed aligners), --check-targets looks the targets up in the GTF or FASTA, and --json prints the findings with a summary for scripts and agents.
To run the entire pipeline from genome index construction to final probe generation in one go:
uprobe run -p protocol.yaml -g genomes.yaml -o ./results --threads 10
Useful flags:
uprobe run -p protocol.yaml -g genomes.yaml -o ./results --continue-invalid --raw --threads 10
--continue-invalid: a gene target that is not a gene_name of the GTF stops the run before anything is designed (status error, exit 1), with an error that lists those targets. With --continue-invalid, they are skipped and reported instead: logged, listed in summary.json (inputs.invalid_targets and an invalid_targets warning), and the run ends partial (or no_valid_targets if no target is left). uprobe validate --check-targets finds them before a run. Tasks submitted through the web server skip invalid targets.--raw: also write every candidate with its attributes, before filtering (<name>_<time>_raw.xlsx).--threads (default 10): threads for building the indexes and for every threaded step of the attributes (Bowtie, Bowtie2, Jellyfish) whose own threads option is not set; an attribute's threads takes precedence. uprobe post-process takes --threads too; web-server tasks use the server's task_threads.Every run writes summary.json to the output directory, also when it stops early or fails. It gives the outcome (status: ok, partial, no_probes, no_valid_targets, no_target_sequences, no_probes_constructed or error, with a message; partial means some targets have no probes, or fewer than equal_space requested), how many candidates each target kept after every post-processing stage and which stage removed the most, statistics of the summary attributes, the off-target genes found by mapped_genes attributes (off_targets), the transcripts each gene target's windows came from (targets.<name>.transcripts, see transcripts below), the output file names, warnings, and the inputs, including the Tm/fold conditions in effect and the genome's Ensembl source when it is recorded. The final probe table is written as <name>_<time>.xlsx and as plain tab-separated <name>_<time>_probes.tsv. Excel limits never fail a run or lose data: a cell longer than Excel's 32,767 characters (e.g. the off-target list of a repeat window) is cut in the .xlsx and ends with ...[truncated: N characters; full value in <file>.tsv], and a table with more rows than a worksheet holds (1,048,576 with the header) is written only as TSV; the raw table then gets a <name>_<time>_raw.tsv too, and summary.json lists the affected tables in an excel_limits warning (output_files.raw_tsv). Web-server result archives include the TSV files. With --json, uprobe run prints the summary to stdout and sends logs and tool output to stderr.
Exit status of uprobe run (summary.json and the --json output are written before it exits):
| Code | Meaning | status |
|---|---|---|
| 0 | Probes were designed | ok, partial |
| 1 | The run failed | error |
| 2 | Invalid command-line usage (missing or bad options) | none; no summary is written |
| 3 | The run finished without any probe | no_probes, no_probes_constructed, no_target_sequences, no_valid_targets |
For advanced users who need intermediate results or custom workflows, you can execute each step individually:
# 1. Build genome index
uprobe build-index -p protocol.yaml -g genomes.yaml -t 10
# 2. Validate target genes against the GTF file
uprobe validate-targets -p protocol.yaml -g genomes.yaml --continue-invalid
# 3. Extract target region sequences
uprobe generate-targets -p protocol.yaml -g genomes.yaml -o ./results --continue-invalid
# 4. Construct initial probes from target sequences
uprobe construct-probes -p protocol.yaml -g genomes.yaml --targets ./results/target_sequences.csv -o ./results
# 5. Post-process probes (requires target + probe columns)
uprobe post-process -p protocol.yaml -g genomes.yaml --probes ./results/constructed_probes_combined.csv -o ./results
# 6. Generate HTML analysis report
uprobe generate-report -p protocol.yaml -g genomes.yaml --probes ./results/probes_*.csv -o ./results
uprobe generate-barcodes \
--strategy max_orthogonality \
--name barcodes \
--num-barcodes 16 \
--length 8 \
--alphabet ACT \
--output ./results/barcodes
This writes barcodes.csv (column sequence) and barcodes.txt.
If you are developing a web backend or data analysis pipeline, we recommend directly using UProbeAPI. It returns Pandas DataFrames, making it easy to process further.
import uprobe
from pathlib import Path
# Initialize API
api = uprobe.UProbeAPI(
protocol_config=Path("protocol.yaml"),
genomes_config=Path("genomes.yaml"),
output_dir=Path("./results")
)
# --- Method 1: Complete Workflow ---
df_final = api.run_workflow(threads=10)
# --- Method 2: Step-by-Step Execution ---
api.build_genome_index(threads=10)
api.validate_targets() # raises on a target not in the GTF; continue_on_invalid=True skips it
df_targets = api.generate_target_seqs()
df_probes = api.construct_probes(df_targets)
import pandas as pd
df_combined = pd.concat([df_targets, df_probes], axis=1)
df_final = api.post_process_probes(df_combined)
# Generate HTML report
reports = api.generate_report(df_final)
print(reports["html_reports"])
# Quick barcode generation
barcodes = api.quick_generate_barcodes(num_barcodes=16, length=8, alphabet="ACT")
U-Probe relies on two main configuration files to run. Here is a breakdown of how to structure them.
genomes.yamlThis file maps a genome name to its corresponding file paths. It tells U-Probe where to find the reference genome and annotation files, and which aligner indices to build.
# Example genomes.yaml
GRCh38:
fasta: "/path/to/GRCh38.fasta"
gtf: "/path/to/GRCh38.gtf"
align_index:
- bowtie2
- bowtie # Bowtie 1 transcriptome index for mapped_genes with aligner: bowtie (exhaustive off-target search)
- blast
jellyfish: false
uprobe build-index builds the indexes of the aligners in align_index that the protocol's attributes search, as uprobe run does before designing: mapped_genes searches its aligner's transcriptome index (transcript.fa, spliced from the GTF), mapped_sites the genome index. An RNA design that searches only the transcriptome therefore never builds the Bowtie2 genome index (13.5 min and 3.5 GB for mouse GRCm39). uprobe build-index --all builds every index of align_index: genome indexes for bowtie2, blast and mmseqs, transcriptome indexes for bowtie2, blast and bowtie, which has no genome index.
Entries written by uprobe genome fetch also record their origin under source (Ensembl division, release, assembly and URLs), which each run copies into summary.json; the verified checksums are kept in genome_source.json next to the files. The workflow ignores keys it does not use.
protocol.yamlThis is the core configuration file that defines all parameters for a specific probe design run. It is highly customizable.
# Example protocol.yaml snippet
name: my_rna_probe_design
genome: GRCh38
targets:
- CD4
extracts:
target_region:
source: mRNA # genome / mRNA / exon / CDS / UTR
length: 30
overlap: 15
# Define how genes map to specific barcodes
encoding:
CD4:
BC1: ACGAGCCTTCCA
BC2: CGGTAATGGACT
# Define the structure of your probes
probes:
probe_1:
template: "{part1}{part2}"
parts:
part1:
expr: "rc(target_region[0:20])"
part2:
template: "CC{barcode1}TGCGTCTATTT{barcode2}TAGTGGAGCCT"
parts:
barcode1:
expr: "encoding[target]['BC1']"
barcode2:
expr: "encoding[target]['BC2']"
probe_2:
template: "{part1}AGGCTCCACTA"
parts:
part1:
expr: "rc(target_region[-10:])"
# Optional. If omitted or empty, the CLI auto-generates attributes,
# post_process, and summary based on source mode and probe structure.
attributes:
target_gc:
target: target_region
type: gc_content
target_tm:
target: target_region
type: annealing_temperature
target_fold:
target: target_region
type: fold_score
post_process:
filters:
target_tm:
condition: target_tm >= 50 & target_tm <= 90
sorts:
is_ascending:
- target_gc
- target_fold # lower fold_score = less structure
summary:
report_name: rna_report # rna_report / dna_report
attributes:
- target_gc
- target_tm
- target_fold
protocol.yaml:name & genome: Basic metadata. The genome must match a key in your genomes.yaml.
targets: A list of target gene names or IDs you want to design probes for.
extracts: Parameters for extracting target sequences: the sliding window length and step (or overlap), and the source the windows are taken from:
genome: targets are regions such as chr1:1000-2000, tiled along the genomic sequence (the reference, + strand). Regions are 1-based and inclusive, as genome browsers and Ensembl show them: chr1:1000-2000 is the 1,001 bases from position 1,000 to 2,000, and the first base of a chromosome is 1. avoid_otp target_regions use the same notation. A window's sub_region s-e is its position along the region, so it covers the genomic bases start + s - 1 to start + e - 1. Earlier versions read region targets as 0-based (chr1:1000-2000 was bases 1,001-2,000, while avoid_otp already read them 1-based), so a DNA design made from the same protocol now starts one base earlier; a start of 0 is an error.mRNA: the whole mature RNA of each target gene, i.e. all its exon features: 5' UTR, CDS and 3' UTR of a protein-coding gene, the processed RNA of a non-coding gene. Use it when probes may bind anywhere on the mRNA, as smFISH probe sets do.exon: the coding sequence of a protein-coding gene (its CDS features, without the UTRs), and all exons only of a gene without CDS. Despite its name it is not the whole mRNA of a protein-coding gene; uprobe validate warns and points to mRNA.CDS: the same as exon, under the name that says what it extracts.UTR: the UTR features (UTR in GENCODE GTFs, five_prime_utr and three_prime_utr in Ensembl GTFs).With transcripts: all (the default), each window lies within one exon (or CDS or UTR segment), so no window spans a splice junction. With exon, CDS and UTR, segments shorter than the window are left out, and a target left with none gets no windows (a log warning and no_candidate_windows in ) while the other targets are designed as usual; with , short exons give no window but still count in the positions along the RNA (, see ). When selects one transcript of a target, , and tile its spliced sequence instead, so windows also span its exon junctions; see .
The post_process steps always run in this order, whatever their order in the protocol: filters, avoid_otp (DNA mode), remove_overlap, off_target_pool, equal_space, sorts.
When extracts.target_region.transcripts selects exactly one transcript of a target (canonical, mane, or a list with one of its ids), U-Probe tiles the spliced sequence of that transcript, as one would tile its cDNA: source: mRNA joins all its exons, exon and CDS its CDS pieces (its exons if it has no CDS), 5' to 3', every one however short. Windows therefore also span its exon-exon junctions. They are valid windows of the spliced RNA and bind only the mature RNA, not the pre-mRNA. On Ensembl 113, the MANE Select mRNA of ACTB (ENST00000646664, 6 exons) gives 1,793 20-nt windows instead of the 1,698 that lie within one exon, exactly the 20-mers of its Ensembl cDNA; ACTB, EEF1A1, GAPDH, HSPA8, RPL13A and TUBB give 12,401 instead of 11,679 (19 more per junction). transcripts: all (the merged exon model), several selected transcripts of a target, and source: UTR keep one window per exon or segment.
target_start/target_end: the window's position on the spliced transcript (or spliced CDS), which remove_overlap, equal_space, off_target_pool and exempt_copies use as for any window.exon_name: the exon (or CDS piece) the window lies on, chrom_start_end_strand, or, for a window across junctions, the exons it spans, 5' to 3', joined by ;, e.g. 7_5530524_5530601_-;7_5529535_5529663_-.start/end and sub_region: the window's position along the exons that exon_name names, joined; for a window within one exon, its position on that exon, as for every other window. ACTB's window at 70-89, on the exons above, covers the last 9 nt of the 78-nt first exon and the first 11 of the second. exon_name and start/end thus place every window on the genome.transcript_names: the transcript; n_trans: 1.mapped_genes and exempt_copies search the spliced transcripts (transcript.fa), so they find a junction window on its own transcript like any other window; the sequence attributes (Tm, GC, folding, self-match) read the window itself. mapped_sites and kmer_count search the genome, where an intron interrupts a junction window: mapped_sites finds the window's own site in part (a local alignment) or not at all, and kmer_count finds its k-mers that cross the junction only in processed copies of the gene, so their values for junction windows do not compare with those of other windows. Use mapped_genes to judge the specificity of RNA probes.equal_space works in DNA (source: genome) and RNA mode (mRNA, exon, CDS, UTR). It runs after the filters, remove_overlap and off_target_pool, orders each target's remaining candidates along the target and keeps every k-th one (evenly spaced ranks, first and last included), so the kept probes follow the candidates from one end of the target to the other. Use it together with remove_overlap; otherwise overlapping windows can be kept (uprobe validate warns).
Identical windows count once: windows of a target with the same sequence are one oligo, so equal_space ranks each distinct oligo once, at its first site. This includes a window that several transcripts or exon entries share at one position (transcripts: all without remove_overlap): earlier versions ranked each copy and could space the probes by duplicates; now it is one candidate, whose row lists the transcripts of all the copies in transcript_names (and their number in n_trans), as remove_overlap does. Without remove_overlap the kept probes of a transcripts: all design can therefore differ from earlier versions (GAPDH, which has no repeat: 77 of 92 MERFISH windows). With remove_overlap before it, as in every template, the candidates are already distinct and the result is unchanged.
Along the target: in DNA mode, along the genomic region. In RNA mode, windows number start/end within their exon or UTR, so U-Probe also writes target_start/target_end: the window's position along the target. When transcripts selects one transcript of the target, that is the position on the spliced transcript (mRNA) or its spliced CDS (exon, CDS); see Windows across exon junctions. Otherwise it is the position on the gene's merged exon model, the union of the extracted exons (CDS or UTRs) of its transcripts with the introns removed, read 5' to 3': for a gene with one transcript and source: mRNA the position on the spliced RNA, while exons of different transcripts that overlap share positions, and with exon, CDS and UTR, segments shorter than the window length are not extracted and do not count. Direct sequences use their own coordinates.
Fewer candidates than requested: the target keeps all of them. summary.json lists requested_probes and final for every target and a fewer_probes_than_requested warning with the counts, the run status is partial, and uprobe run logs the per-target counts as final/requested.
By default, Tm is computed under PCR-like conditions (legacy defaults: 50 mM Na+, 0.6 mM dNTP, 50 nM oligo, no formamide). FISH hybridizations usually run in SSC buffer with formamide, so declare the conditions of your experiment with a top-level hybridization block:
hybridization:
temperature_c: 37 # hybridization temperature (°C); used by fold_score and auto-generated Tm filters
na_mM: 390 # monovalent cations; 2x SSC (0.3 M NaCl + 0.03 M Na3-citrate) = 390 mM Na+
mg_mM: 0 # divalent cations
dntp_mM: 0 # dNTPs (chelate Mg2+; no effect when mg_mM is 0)
probe_nM: 50 # concentration of each probe oligo
formamide_pct: 30 # formamide, % (v/v)
attributes:
probe_1_tm:
target: probe_1.part1
type: annealing_temperature # uses the block above
readout_tm:
target: probe_2
type: annealing_temperature
hybridization: # per-attribute override, merged over the block
formamide_pct: 10
| Field | Unit | Default when unset | Used by |
|---|---|---|---|
temperature_c | °C | 37 (ViennaRNA default) | fold_score, auto-generated Tm filters |
na_mM | mM | 50 | annealing_temperature |
mg_mM | mM | 0 | annealing_temperature |
dntp_mM | mM | 0.6 | annealing_temperature |
probe_nM | nM | 50 | annealing_temperature |
formamide_pct | % (v/v) | 0 | annealing_temperature |
annealing_temperature or fold_score attribute may carry its own hybridization mapping with the same fields, overriding the top-level values for that attribute only.>= 0; na_mM and probe_nM must be > 0, formamide_pct and temperature_c at most 100, and unknown field names are rejected.tm_method: multi_padlock_design (on an annealing_temperature attribute): the Tm formula of Moldia/multi_padlock_design (lib/screenseq.py), the code that designed the HybISS padlock probes (Gyllborg et al. 2020, Nucleic Acids Res 48:e112, doi:10.1093/nar/gkaa792): Sugimoto et al. 1996 DNA/DNA nearest-neighbor parameters (Nucleic Acids Res 24:4501-4505, doi:10.1093/nar/24.22.4501) with one initiation term, the probe concentration as strand concentration, + 16.6 log10([Na+] + 3.795 sqrt([Mg2+])) (mol/l) and - 0.72 °C per % formamide; dntp_mM is not used. It reproduces the repository's values (to 1e-12 °C) under its fixed conditions, 75 mM Na+, 10 mM Mg2+, 100 nM probe and 20% formamide, and lets the padlock_iss template apply the published 65-75 °C window: on the 1,195 published HybISS targets Primer3 gives 3.4 ± 1.5 °C less, and only 327 of them fall within 65-75 °C by Primer3.tm_method: merfish_design: the Tm formula of the MERFISH probe design code (Moffitt et al. 2016, PNAS 113:11046, doi:10.1073/pnas.1612826113; ZhuangLab/MERFISH_analysis, probe_construction/TRDesigner.m, GetRegionTm): SantaLucia & Hicks 2004 nearest-neighbor sums (the 1998 values except AA/TT, -7.6 kcal/mol and -21.3 cal/K/mol), + 0.2 / -5.7 initiation and + 2.2 / + 6.9 per terminal A or T, 0.368 (N-1) ln[Na+], and Tm = 1000 dH / (dS + R ln C) - 273.15 with C the probe concentration itself, where Primer3 uses C/4 (the code's own comment says the correct term would be probe minus half the target concentration). So the formula is about 1.4 °C warmer than Primer3 for a 30-mer at 5 nM. Uses and only; the published 66-76 °C window of 30-nt targets is computed at 300 mM Na+ and 5 nM, with no formamide. The code is ported from reading; the paper and repository give no per-probe Tm values to compare with, so the formula is checked against a hand calculation of the code's terms, and against Primer3, whose difference is the concentration term.A mapped_genes attribute aligns a sequence to the transcriptome and counts the distinct genes it hits, the target gene included, so target_mapped_genes <= 1 keeps sequences that hit no other gene. Choose the search with aligner:
aligner | Search | Needs |
|---|---|---|
bowtie2 | Bowtie2 --very-sensitive-local -k 100: local alignments, which may cover only part of the sequence, found by a seed heuristic | bowtie2 in align_index |
bowtie | Bowtie 1 -v <max_mismatches> -a: every end-to-end site with at most max_mismatches substitutions (0-3, default 2); indels are not considered | bowtie in align_index and Bowtie 1 installed |
attributes:
target_mapped_genes:
target: target_region
type: mapped_genes
aligner: bowtie
max_mismatches: 2
strand: sense
strand (both aligners): both (default) counts sites on either transcript strand; sense only sites where the sequence itself occurs in a transcript, right for target_region, whose probes bind exactly there; antisense only sites of its reverse complement, right for a probe's binding sequence. A probe does not bind an RNA that contains its own sequence, so both also counts antisense transcripts the probe cannot bind.uprobe run logs a warning and uprobe validate reports it; use aligner: bowtie for smFISH-length oligos. uprobe validate also warns when Bowtie2 aligns sequences of 22-30 nt, whose off-target sites it finds only in part (see below).aligner: bowtie (max_mismatches: 2) reported exactly the sites a brute-force scan of every transcript window found, for a random sample of 1,000 of them (147,426 sites); bowtie2 found 1.5% of the off-target genes with a site within 2 substitutions (none for 18-21-mers; for 30- and 40-mers it found 56% and 82%). The Bowtie 1 search took 4.6 s for the 15,295 sequences with 8 threads (2.5 s with strand: sense; Bowtie2 1.6 s); its index took about 5 minutes to build and 650 MB of disk.target_mapped_genes <= 1 sees: for 1,500 random windows of each length from 400 random MANE Select mRNAs, searched in the 410,909 Ensembl 113 human cDNA and ncRNA transcripts (patches included, genes counted by name) with strand: sense, Bowtie2 flagged none of the 20-mers, 69% of the 25-mers, 88% of the 28-mers, 87% of the 30-mers and 97-99% of the 40-, 46- and 52-mers that have a site within 2 substitutions in another gene, listing on average 0%, 49%, 71%, 70% and 87-94% of those genes. It reports at most 100 alignments, so it misses most genes of windows in repeats; from 40 nt on it also reports partial matches (about 25 nt or more), indels and sites with 3 or more mismatches, which a search within 2 substitutions cannot see (3-5% of the windows had only such hits). The templates therefore use aligner: bowtie for windows of 30 nt or less (smFISH, seqFISH+, MERFISH, HybISS) and Bowtie2 for the 40-52-nt windows of MiP-seq, STARmap, the double-hybridization RCA design and HCR v3, all with strand: sense.attributes: in RNA mode the generated uses (, ) when the genome's includes and Bowtie 1 is installed (the run builds the index if it is missing), and otherwise; the log says which and why, with a warning when the windows are shorter than 22 nt. makes the same choice.A probe can only bind RNAs that are present. Supply an expression table of your sample, and mapped_genes attributes no longer count off-target genes that are not expressed, or are expressed far below the target:
gene_expression:
table: hek293_tpm.tsv # your table: tab-separated (comma-separated when the name contains .csv), may be gzipped
level: gene # default; transcript for a table of transcripts (recommended, see below)
gene_column: gene_id # Ensembl gene ids (with or without version) or gene names; default: the first column
value_column: TPM # default: the only numeric column
min_value: 1 # off-target genes with a lower value are not counted
min_fraction_of_target: 0.05 # optional: nor those below 5% of the target's value
absent: count # default; ignore: off-target genes missing from the table are not counted
gene_column may list several columns, e.g. [gene_id, gene_name]. A gene listed more than once counts with its highest value. Leading # comment lines are skipped, and GTEx .gct files can be used directly (choose the tissue with value_column).<attribute> and is listed in <attribute>_unexpressed instead of <attribute>_off_targets. The threshold is min_value, or with min_fraction_of_target the higher of the two below. The target gene always counts, so <attribute> <= 1 still means "no (expressed) off-target gene".min_fraction_of_target (optional, 0-1) makes the threshold relative to the target: an off-target gene of a target counts only if its value is at least max(min_value, min_fraction_of_target x the target's value). A spot needs many probes on one RNA, and an RNA at a few percent of the target's level adds at most a few percent of false spots, so low-level pseudogenes and paralogs of a highly expressed target no longer remove its probes, while a gene expressed at a similar level still does. The target's value is read from the same table (the highest of its gene ids). A target that the table does not list keeps min_value alone: the log warns, and summary.json lists it in off_targets.gene_expression.targets_not_in_table (warning targets_not_in_expression_table), next to the value and threshold of every listed target (off_targets.gene_expression.targets). Per attribute, below_target_fraction counts the off-target genes that only the fraction removed, for at least one target.absent: count, the conservative default). With absent: ignore they are not counted and are listed in <attribute>_unexpressed, for a table you trust to list every gene the sample expresses. Either way the log and summary.json (off_targets: missing_from_table per attribute, with a warning off_target_genes_not_in_expression_table) report how many there were, with examples, and which of them the final probes hit. A table that matches no gene of the annotation stops the run: its identifiers or species do not fit the genome.mapped_genes attribute. Relative paths are resolved from the directory where uprobe runs. uprobe validate checks the block, the file and its columns.Use a table of transcripts when you have one, e.g. salmon's quant.sf, kallisto's abundance.tsv or RSEM's isoforms.results. False signal comes only from RNA molecules that carry a probe's site, so an off-target gene's value for a sequence is then the sum of the values of the gene's transcripts that the sequence hits, not the whole gene's. A gene expressed on isoforms without the site no longer counts.
gene_expression:
table: quant.sf
level: transcript
transcript_column: Name # default: the first column
value_column: TPM
min_value: 1
min_fraction_of_target: 0.05
ENSMUST00000073605.15 matches ENSMUST00000073605; a GENCODE FASTA header such as ENST...|ENSG...|... by its first field). The genome's GTF, from which the searched transcriptome is built, assigns them to genes, so gene_column does not apply.min_fraction_of_target, is the sum over all its transcripts in the table.absent: count) or adds nothing to the gene's value (absent: ignore). missing_from_table then counts the genes with such transcripts, and missing_transcripts the transcripts.<attribute>_hit_transcripts lists, for each off-target gene the table valued, the transcripts the sequence hits with their values (NA when not listed), e.g. Mir100hg: ENSMUST00000212079=0.014, ENSMUST00000362798=0. <attribute>_off_targets and off_target_pool still work by gene, and summary.json reports the level (off_targets.gene_expression.level).level: transcript, or transcript ids with level: gene) stops the run with a hint, and uprobe validate reports it.Measured with template runs of the smFISH template as in Copies of the target (reference mRNA, 20-mers, Bowtie 1 within 2 substitutions, exempt_copies, target_mapped_genes <= 1, 48 probes per gene) with TPM >= 1 and min_fraction_of_target: 0.05. The tables are salmon quantifications of HeLa-S3 and NIH/3T3 on the Ensembl 113 transcriptomes, by gene and by transcript; a gene's TPM is the sum of its transcripts' TPM, so both tables come from one quantification. Final probes (genes with 48), candidate windows without a counted off-target gene, and off-target genes counted for at least one window:
| Table | Human ACTB, EEF1A1, GAPDH, HSPA8, RPL13A, TUBB | Human, 30 random genes | Mouse Actb, Eef1a1, Gapdh, Hspa8, Rpl13a, Rpl3, Tubb5 | Mouse, 30 random genes |
|---|---|---|---|---|
| By gene | 288 (6); 9,314; 2,777 | 1,337 (26); 64,631; 12,894 | 313 (5); 9,015; 1,840 | 1,426 (28); 84,142; 11,245 |
| By transcript | 288 (6); 10,233; 1,078 | 1,350 (26); 74,107; 11,589 | 335 (6); 9,984; 724 | 1,427 (28); 91,248; 9,999 |
A processed pseudogene, a retrocopy, or a non-coding gene that overlaps the target can hold most of the target's sequence. Every probe of the target then hits it too, so no probe set avoids it, and counting it as an off-target leaves such a target with few or no probes, although probe sets for it work in practice. With exempt_copies, a mapped_genes attribute does not count these genes, as long as they are not protein-coding:
attributes:
target_mapped_genes:
target: target_region
type: mapped_genes
aligner: bowtie
max_mismatches: 2
strand: sense
exempt_copies: # or `exempt_copies: true` for these defaults
kmer: 30 # k-mer length, 12-32
min_shared: 0.5 # share of the target's k-mers that one transcript of the gene must hold
min_shared of the distinct kmer-mers of the target's sequence. A copy is not counted in <attribute> and is listed in <attribute>_copies (name and fewest mismatches, as in _off_targets) instead of <attribute>_off_targets, so off_target_pool does not limit it either. It is exempt whatever its expression; gene_expression applies to the other off-target genes. Paralogs share few exact 30-mers and stay counted.protein_coding or an expressed immunoglobulin or T-cell receptor segment (IG_C_gene, TR_C_gene, ...: mouse Trbc2 for Trbc1); their pseudogenes stay eligible. The biotype is the GTF's gene_biotype attribute (Ensembl), or gene_type (GENCODE). A protein-coding copy is a separately quantified transcript, and binding it is a real off-target: mouse Ifit3b holds 62.5% of the 30-mers of Ifit3, and stays counted. So does a copy whose biotype the GTF does not give, with a log warning naming how many such genes there are. summary.json lists both under copies_not_exempt, each with its share and not_exempt: protein_coding (or unknown_biotype).target_start/target_end: in RNA mode its merged exon model (the extracted segments of its transcripts, all of them or those transcripts selects, with the introns removed: the coordinates remove_overlap and equal_space use), so the share is that of the sequence the probes are drawn from, e.g. of the MANE Select mRNA with source: mRNA and transcripts: mane; for a direct sequence, the sequence itself. With source: mRNA that is the whole mRNA; with exon or CDS, U-Probe extracts the CDS of protein-coding genes, so their copies are judged on the coding sequence. Bases that no window covers are unknown, so keep step at most the window length (uprobe validate warns).transcript.fa, the FASTA the transcriptome index is built from (extracted again if it is missing), in one pass, with the k-mers packed into 64-bit integers. Checking every gene of the Ensembl 113 human transcriptome (410,909 transcripts, 597 Mb) against 4 targets took 9 s; in the runs below the check added 3-13 s to a run.Shares of the target's 30-mers on Ensembl 113, for the canonical mRNA and for the merged CDS that source: exon extracts; at min_shared: 0.5 the paralog and the human GAPDH pseudogenes stay counted:
| Target | Off-target gene | Canonical mRNA | Merged CDS |
|---|---|---|---|
| ACTB | ACTG1 (paralog) | 4.8% | 7.4% |
| GAPDH | GAPDHP1 (the closest of its ~70 pseudogenes) | 23.5% | 29.9% |
| EEF1A1 | EEF1A1P5 (processed pseudogene) | 35.6% | 81.2% |
| RPL13A | RPL13AP5 (processed pseudogene) | 44.9% | 64.9% |
| mouse Gapdh | Gm13394 (retrocopy, 668 TPM in NIH/3T3) | 75.6% |
Measured before exempt_copies was limited to genes that are not protein-coding, so protein-coding copies (e.g. mouse Vmn2r92 for Vmn2r91) were exempt in these runs. Measured with template runs of the smFISH template on Ensembl 113 (GRCh38 and GRCm39 primary assemblies with their GTFs): windows from the whole reference mRNA, across its exon junctions (source: mRNA, transcripts: mane: MANE Select in human, Ensembl canonical in mouse), 20-mers, Bowtie 1 within 2 substitutions, target_mapped_genes <= 1, remove_overlap 2, 48 probes per gene, with HeLa-S3 and NIH/3T3 tables (TPM, min_value: 1). Final probes (genes with 48); each row adds its option to the row above, except exempt_copies alone, which is the template's default:
| Setting | Human ACTB, EEF1A1, GAPDH, HSPA8, RPL13A, TUBB | Human, 30 random genes | Mouse Actb, Eef1a1, Gapdh, Hspa8, Rpl13a, Rpl3, Tubb5 | Mouse, 30 random genes |
|---|---|---|---|---|
| No expression table | 119 (1) | 1,198 (24) | 120 (1) | 1,288 (23) |
exempt_copies alone (template default) | 119 (1) | 1,231 (24) | 125 (1) | 1,300 (23) |
| Table, TPM >= 1 | 180 (2) | 1,255 (25) | 125 (1) | 1,394 (27) |
+ min_fraction_of_target: 0.05 | 193 (2) | 1,285 (25) | 203 (2) | 1,394 (27) |
+ exempt_copies (recommended) | 193 (2) | 1,333 (26) | 275 (4) | 1,394 (27) |
+ absent: ignore | 264 (5) | 1,337 (26) | 335 (6) | 1,426 (28) |
absent: ignore trusts the table to list every expressed gene.Where the windows come from matters as much. Up to U-Probe 0.2 the template used source: exon, the CDS of all coding isoforms; mRNA with transcripts: all takes the exons of every isoform, retained introns included. Probes (genes with 48) and the share of final probes whose site lies on the reference mRNA, without an expression table (exempt_copies on) and with the recommended settings (TPM >= 1, min_fraction_of_target: 0.05, exempt_copies):
| Windows | Expression table | Human ACTB, EEF1A1, GAPDH, HSPA8, RPL13A, TUBB | Human, 30 random genes | Mouse Actb, Eef1a1, Gapdh, Hspa8, Rpl13a, Rpl3, Tubb5 | Mouse, 30 random genes |
|---|---|---|---|---|---|
exon (CDS), all transcripts (template up to 0.2) | none | 11 (0), 45% | 1,021 (13), 95% | 38 (0), 68% | 1,082 (17), 98% |
exon (CDS), all transcripts (template up to 0.2) | recommended | 125 (2), 94% | 1,119 (16), 95% | 222 (3), 95% | 1,209 (19), 99% |
mRNA, all transcripts | none | 288 (6), 16% | 1,334 (27), 68% | 332 (5), 27% | 1,337 (26), 76% |
mRNA, all transcripts | recommended | 288 (6), 31% | 1,376 (28), 72% | 336 (7), 38% | 1,400 (28), 77% |
mRNA, transcripts: mane (template) | none | 119 (1), 100% | 1,231 (24), 100% | 125 (1), 100% | 1,300 (23), 100% |
mRNA, transcripts: mane (template) | recommended | 193 (2), 100% | 1,333 (26), 100% | 275 (4), 100% | 1,394 (27), 100% |
The reference mRNA gave the six human housekeeping genes 119 probes against 11 from the CDS of all isoforms without a table, and 193 against 125 with it; paralogs and pseudogenes share their coding sequence more than their UTRs. All transcripts reach 48 probes for nearly every housekeeping gene, but mostly on exons of other isoforms and retained introns: only 16-38% of these probes lie on the reference mRNA.
In smFISH a false spot needs many probes of the set on the same off-target RNA, so a few probes that hit one off-target gene do no harm, while many do. off_target_pool limits how many probes of one target's set may hit the same off-target gene:
post_process:
filters:
target_mapped_genes:
condition: target_mapped_genes <= 2 # at most one off-target gene per probe
remove_overlap:
location_interval: 2
off_target_pool:
attribute: target_mapped_genes # a mapped_genes attribute, or a list of them
max_probes_per_gene: 2
equal_space:
number_desired: 48
sorts:
is_ascending: [target_mapped_genes]
sorts defines (the current order when there are no sorts), and a probe is dropped when one of its off-target genes (<attribute>_off_targets, all listed attributes together) is already hit by max_probes_per_gene kept probes. With a gene_expression table only expressed off-target genes are listed, so only they are limited; copies of the target (exempt_copies) are not listed either. max_probes_per_gene: 0 keeps only probes without off-target genes.remove_overlap, so the limit applies to probes that can end up in the same set, and before equal_space, which then chooses the final N among the probes that respect it (any subset still does). Per-probe filters such as target_mapped_genes <= 3 still apply first.summary.json reports the stage in stages and, in off_targets.pool, how many probes it removed and the genes that reached the limit most often.target_mapped_genes <= 2 (at most one off-target gene per probe).equal_space picks the final probes by position, not by their off-target genes, so when a target has more candidates than it needs, the limit mostly swaps clean probes for probes with an off-target gene instead of adding probes (measured below). It would pay off with an equal_space that prefers probes without off-target genes, which U-Probe does not have yet.target_mapped_genes <= 1 (with gene_expression).Measured on Ensembl 113 human genes with template runs of the smFISH template (the MANE Select mRNAs, exempt_copies, off-targets within 2 substitutions by Bowtie 1, remove_overlap 2, 48 probes per gene; the genes of Copies of the target): target_mapped_genes <= 1 alone, against target_mapped_genes <= 2 with max_probes_per_gene: 2.
| Genes | Expression table | <= 1: probes (genes with 48) | <= 2 + pool: probes (genes with 48) | Final probes with an off-target gene, <= 2 + pool |
|---|---|---|---|---|
| 30 random protein-coding genes | none | 1,231 (24) | 1,249 (24) | 47% |
| 30 random protein-coding genes | HeLa, TPM >= 1 | 1,303 (26) | 1,312 (26) | 34% |
| ACTB, EEF1A1, GAPDH, HSPA8, RPL13A, TUBB | none | 119 (1) | 132 (1) | 45% |
| ACTB, EEF1A1, GAPDH, HSPA8, RPL13A, TUBB | HeLa, TPM >= 1 | 180 (2) | 195 (2) | 36% |
The limit added 9-18 probes per gene set while a third to almost half of the final probes came to carry an off-target gene (each such gene hit by at most 2 probes of a set). An expression table gained more: 61 and 72 probes with <= 1. The smFISH template therefore keeps target_mapped_genes <= 1, recommends gene_expression and leaves off_target_pool as a commented-out option for genes with many paralogs or pseudogenes.
For more detailed examples and advanced configurations, please refer to the tests/data/*.yaml directory and the built-in protocol templates (uprobe templates list).
If you use U-Probe in your research, please cite our paper (or software):
@software{uprobe2026,
title={U-Probe: Universal Probe Design Tool},
author={Zhang, Qian and Xu, Weize and Cai, Huaiyuan},
year={2025},
url={https://github.com/u-science-lab/U-Probe},
version={1.0.0}
}
(Note: Update the citation format with actual journal details once published.)
U-Probe is released under the MIT License. See the LICENSE file for details.
We thank the bioinformatics community for valuable feedback during development, and the authors of the following tools that U-Probe integrates:
--dry-rungenome add again for the same name rewrites the annotation and transcript.fa. When the transcript sequences or their names (e.g. a changed gene id) differ from the previous transcript.fa, the indexes built from it in the entry's index_dir (<aligner>_transcript/) are removed, and so are those of the FASTA (<aligner>_genome/, jf_genome/) when the FASTA differs; runs rebuild the ones they need. An old index would otherwise report the target's own gene under its previous id as an off-target gene.summary.jsonmRNAtarget_starttranscriptsmRNAexonCDStranscripts (RNA sources): which transcripts of each gene the windows come from. all (the default) takes the exons of all its transcripts, the merged exon model, so windows can lie on exons that only rare isoforms, or retained-intron transcripts, contain. canonical takes the transcript the GTF tags Ensembl_canonical; mane the one tagged MANE_Select (human), and for a gene without one (other species, most non-coding genes) its Ensembl_canonical transcript, with a log note; a list of transcript ids, e.g. transcripts: [ENST00000646664], takes those (with or without version), and every target needs at least one of them. Ensembl and GENCODE GTFs carry both tags. An unknown id, an id of a gene that is not a target, a target without a listed id, or a gene without the tag stops the run with an error; uprobe validate --check-targets reports them beforehand. With one transcript, the windows tile that spliced transcript (mRNA) or its spliced CDS (exon, CDS), across its exon junctions, target_start is the position on it, and exempt_copies judges copies against it. summary.json lists each target's transcripts: selection, chosen_by (all, MANE_Select, Ensembl_canonical or listed) and ids.masked (source: genome): keep (the default) upper-cases the windows; drop skips every window with a soft-masked (lowercase) or N base, as OligoMiner mines a repeat-masked genome. It needs a soft-masked FASTA (uprobe genome fetch --soft-masked, or UCSC hg38.fa) or a mask; the run log and uprobe validate --check-targets warn when a target has no lowercase base (with a mask: and none of its bases is in the mask). The Oligopaint template uses drop.mask (source: genome, with masked: drop): the path of a file that marks repeats on the genome apart from the FASTA, so an unmasked genome can be used and no soft-masked copy has to be made: a BED file (chrom start end, 0-based and end-exclusive, in the coordinates of the genome FASTA, optionally .gz; e.g. RepeatMasker or UCSC rmsk intervals), or a FASTA of soft-masked loci whose record names are regions (>chr7:1000-52000, repeats in lowercase). A window that contains a base of the mask, a soft-masked base of the genome FASTA or an N is dropped. It is read once and applied to the extracted locus. uprobe validate checks that the file reads and that masked: drop is set, and warns, without --check-targets, when the mask has no interval on a target's chromosome (chr1 vs 1: a mask with other chromosome names masks nothing). The run log warns when a target has neither a soft-masked base nor a base in the mask, and names the chromosome when the mask has no interval on it.encoding: Mapping of specific genes to custom barcodes or identifiers (e.g., assigning BC1 and BC2 to CD4).
probes: The core of the design, powered by a Directed Acyclic Graph (DAG) architecture. This allows for complex, modular probe construction where parts and probes can reference each other.
template: Construct sequences using placeholders (e.g., {part1}{part2}).expr: Apply Python-like expressions. You can use built-in functions like rc() (reverse complement), slice sequences ([0:20]), or fetch from the encoding map (encoding[target]['BC1']). Expressions are evaluated safely (never with Python eval): only string/integer literals, the names target_region, target, encoding, target_parts, probe_index and probe/part names (e.g. probe_1.part1), subscripts and slices, + concatenation, % (remainder of two integers) and rc() are allowed; anything else (other function calls, attribute or method access, comprehensions, lambdas, ...) is rejected with an error naming the probe part.probe_index: the number of the probe within its target, 0, 1, 2, ... along the target (5' to 3' on an RNA, along the region on DNA), so a part can vary from probe to probe, e.g. rotate readouts with an encoding list: rc(encoding[target]['readouts'][(probe_index + 1) % 4]) (an encoding value may be a list of sequences). The final probes are numbered after post-processing, the parts that read probe_index are then rebuilt and their attributes recomputed, and the final table gets a probe_index column; filters and sorts see each candidate numbered in extraction order (uprobe validate warns about attributes of such parts). The merfish template uses it to put 3 of each gene's 4 readouts on every probe.attributes: Define the biochemical or physical properties you want to calculate for specific targets or probe parts. Available attribute types include:
gc_content: Calculates the GC ratio of the sequence.base_fraction, max_homopolymer, max_window_count: base composition. base_fraction is the fraction of the sequence's bases that are one of bases (A, C, GC, ...; U counts as T); max_homopolymer the longest run of one base of bases (default: any base), so <= 3 excludes AAAA; max_window_count the highest number of bases in any window consecutive bases. Each takes positions: [start, end], 1-based and inclusive from the 5' end of the sequence as written (negative: from the 3' end, -1 = the last base), to look at part of it only. Together they express sequence rules such as those of Xu et al. 2009 (PNAS 106:2289, doi:10.1073/pnas.0812506106), which Oligostan applies to smiFISH probes: A < 28 % (base_fraction, bases: A, filter < 0.28), no AAAA, C 22-28 %, no CCCC, and no 4 C in any 6 consecutive bases within the first 12 (max_window_count, bases: C, window: 6, positions: [1, 12], filter <= 3). Filters run before remove_overlap, so the overlap removal chooses among windows that pass them.annealing_temperature: Calculates the melting temperature (Tm) using Primer3, under the protocol's hybridization conditions. With tm_method: multi_padlock_design, merfish_design or oligominer it uses the formula of the HybISS padlock, MERFISH or OligoMiner design code instead (see Hybridization conditions).fold_score: The negated minimum free energy, −MFE in kcal/mol (≥ 0), of the sequence folded alone as RNA by ViennaRNA, at hybridization.temperature_c when set. Higher means more stable secondary structure, so lower is better: filter with <= and sort it in is_ascending.rna_dna_dg37: The RNA/DNA hybrid free energy ΔG°37 (kcal/mol; more negative = more stable) of a DNA probe on its RNA target, computed as the Oligostan script does (Tsanov et al. 2016, NAR 44:e165, doi:10.1093/nar/gkw784; Oligostan.r in bitbucket.org/muellerflorian/fish_quant): the sum of 16 tabulated dinucleotide values of the probe read 5'→3' plus a constant salt term of -0.1785 kcal/mol (0.115 M), with no initiation or terminal term; a dinucleotide with an unknown base counts 0. Point target at the probe or its binding part (default, sequence: probe), or at target_region with sequence: target, which reverse-complements the RNA first. target_region (or a target_parts layout part) with sequence: probe reads the RNA as if it were the probe and gives the value of the other strand (up to 8.8 kcal/mol off, median 2.0, on the 68 Oligostan XPO1 probes), so uprobe validate and the run warn about it. It does not use hybridization. Filter on it like any attribute, e.g. condition: dg37 >= -33 & dg37 <= -31 (filter conditions accept negative numbers). It reproduces the dG37 column of the script's own tutorial output (Oligostan_Tutorial_Data_160527.zip, all 68 XPO1 probes of 26-32 nt) to 1e-9. The script gives no reference for its 16 values; the paper cites Wu et al. 2002 (Eur J Biochem 269:2821, doi:10.1046/j.1432-1033.2002.02970.x) for the temperature dependence of RNA/DNA stability, and the values were not checked against Sugimoto et al. 1995 or Wu et al. 2002 (not freely available). Oligostan's own choice of probes (one target ΔG°37 for the whole set, probes within ±1 kcal/mol of it, chosen by sweeping -36 to -28 kcal/mol) is not implemented; the attribute gives the filter, not the sweep.self_match: Counts 4-mer matches between the sequence and its reverse complement, a measure of self-dimerization; lower is better.mapped_sites: Aligns the sequence to the genome (Bowtie2 --very-sensitive-local -k 100, both strands) and lists the sites, with their number in <attribute>_num; <attribute>_num == 1 keeps sequences with one site, OligoMiner's Unique Mode. A window across an exon junction has no contiguous genomic site (Windows across exon junctions).mapped_genes: Counts the number of unique genes the sequence aligns to in the transcriptome, via Bowtie2 or, exhaustively within max_mismatches substitutions, via Bowtie 1. See Off-target search; with exempt_copies, genes that copy most of the target and are not protein-coding are not counted (Copies of the target).difference: The absolute difference of two attributes defined before it, of: [a, b] (no target); the STARmap template uses it for the Tm difference of its two halves.kmer_count: Counts k-mer occurrences in the genome (via Jellyfish) to evaluate specificity: the highest genome count among the sequence's k-mers, so lower is better. Each kmer_len (default 35) has its own index, jf_genome/<fasta stem>.k<kmer_len>.jf next to the FASTA, built at its first use. strand: both (the default) counts canonical k-mers, i.e. on both strands; strand: sense counts the sequence's k-mers as written on the FASTA's forward strand, as OligoMiner's and PaintSHOP's dictionaries do (index <fasta stem>.k<kmer_len>.stranded.jf), and antisense their reverse complements. For windows across exon junctions see Windows across exon junctions. With locus_only: true it counts only the k-mers of the candidate windows of the table: U-Probe lists them (both strands) in a FASTA and runs jellyfish count --if <that FASTA> <genome FASTA> (Jellyfish 2.x; Marçais & Kingsford 2011, doi:10.1093/bioinformatics/btr011), which counts, in one pass over the genome, only the k-mers of that file. The temporary index is a few MB for a locus-sized target instead of the whole-genome table (about 18 GB for human 18-mers), is not kept, and jellyfish: true is not needed in the genome entry. The counts equal those of the whole-genome index for strand: both, sense and antisense (tested with Jellyfish 2.3.0); size defaults to twice the number of listed k-mers.hybridization (optional): The experimental conditions (temperature, salt, probe concentration, formamide) that annealing_temperature and fold_score are computed under. See Hybridization conditions.
gene_expression (optional): An expression table of your sample, by gene or by transcript; mapped_genes attributes then ignore off-target genes that are not expressed, or optionally expressed far below the target. See Expression-aware off-target counting.
post_process: Define strict filters (e.g., Tm ranges) based on the calculated attributes, and sorts to rank the best probes (ascending or descending). Lower is better for fold_score, self_match, kmer_count and mapped_genes: list them under is_ascending (uprobe validate warns when they are sorted descending). When post_process is generated, every sort is ascending: GC, Tm, mapped genes, then the fold, self-match and k-mer scores. Its specificity filter is target_mapped_genes <= 4 in RNA mode and, in DNA mode (source: genome), target_kmer_count <= 100 on the generated target_kmer_count attribute: the highest genome count of the 35-mers of the genomic window (Jellyfish; the genome needs jellyfish: true).
remove_overlap: Optional post-processing step for controlling spacing between probes. Put it under post_process when you need it, e.g. post_process: { remove_overlap: { location_interval: 0 } }. Of the candidates that share a start (a length range or a layout with ranges) it keeps the shortest, or with prefer: <attribute> the one with the lowest value of that attribute. It visits each target's candidates in order of position and keeps one when it starts more than location_interval bases after the end of the last kept one. In RNA mode the positions are those along the target (target_start/target_end: on the gene's merged exon model, or on its one selected transcript; see Probes per target), so windows from alternative exons of different transcripts are compared too, and a site that several exon entries contain is kept once, with the transcripts of all of them in transcript_names (and their number in n_trans). Windows of a target that have the same sequence (an internal repeat, e.g. tandem copies of a domain) are one oligo that binds several sites: it is kept once, only if none of its sites is within location_interval of a kept site, and then occupies all of them (a layout is part of the oligo). equal_space likewise counts such an oligo once. When a kept oligo has several sites, its row gets n_sites and oligo_sites (start-end;start-end, along the target) and summary.json a multi_site_oligos warning listing them; tables without repeats are unchanged. The warning and the columns describe the final table: each of its oligos with several sites is listed once, with all its sites (its count is the number of rows with n_sites > 1), and an oligo that a later step (equal_space) leaves out is not listed; the columns are left out when no final row has several sites.
off_target_pool: Optional post-processing step that lets at most max_probes_per_gene probes of a target's set hit the same off-target gene, e.g. post_process: { off_target_pool: { attribute: target_mapped_genes, max_probes_per_gene: 2 } }. See Off-target pool limit.
equal_space: Optional post-processing step that keeps number_desired probes per target, spread evenly along the target, e.g. post_process: { equal_space: { number_desired: 48 } }, or per target: equal_space: { GAPDH: { number_desired: 48 } }. See Probes per target.
summary: Define the report_name (e.g., rna_report or dna_report) and the specific attributes you want to visualize and output in the final report. The HTML report embeds the final table, a 100-row preview of the raw candidates and a link to the raw table that --raw writes; summary: {report: {embed_raw: true}} also embeds the whole raw table (63 MB for the 581,793 candidates of a 200-kb Oligopaint design).
meta (optional): Free-form documentation that the workflow ignores; the built-in templates use it for citations and parameter sources.
na_mMprobe_nMtm_method: oligominer: the Tm of OligoMiner (Beliveau et al. 2018, PNAS 115:E2183, doi:10.1073/pnas.1714530115; brianbeliveau/OligoMiner, blockParse.py/probeTm.py): Biopython Tm_NN with DNA_NN3 (SantaLucia 1998), salt correction 5 and strand concentration dnac1 - dnac2/2, rounded to 2 decimals, minus 0.65 °C per % formamide (chem_correction). probe_nM is both dnac1 and dnac2 (OligoMiner's -c and -C, 25 nM by default), na_mM is -s (390 mM) and formamide_pct is -F (50); Mg2+ and dNTP are not used. It reproduces the Tm column of OligoMiner's ExampleFiles/3_probes.bed (12 probes, 42.08-45.79 °C) to within its 0.01 °C rounding. OligoMiner's default window, 42-47 °C (-t, -T), applies to this formamide-corrected value. Example: hybridization: {na_mM: 390, probe_nM: 25, formamide_pct: 50} on the attribute.merfish_design and oligominer take only declared conditions; a field they use that is not declared (protocol block or the attribute's override) gets U-Probe's legacy PCR default (50 mM Na+, 50 nM probe, 0% formamide), far from the design codes' conditions: OligoMiner's first example probe gets 64.86 °C instead of 42.85 °C. uprobe validate and the run warn and suggest the design code's values (OligoMiner: 390 mM, 25 nM, 50%; MERFISH: 300 mM, 5 nM).formamide_pct is converted to mol/l with formamide's density (1.1334 g/ml at 20 °C) and molar mass (45.04 g/mol), so 1% (v/v) = 0.252 M and Tm drops 0.61-0.72 °C per percent depending on GC content, close to the linear 0.60 °C/% measured by Hutton 1977 (Nucleic Acids Res 4:3537-3555, doi:10.1093/nar/4.10.3537). It is preferred over a fixed linear factor because it accounts for GC content and is applied inside the same Primer3 call.fold_score folds at temperature_c; ViennaRNA does not model formamide, and salt is left at its default.binding_tm with the MERFISH design code's formula (tm_method: merfish_design) at 300 mM Na+ and 5 nM probe, with no formamide term, the conditions of its 66-76 °C window.uprobe validate reports an invalid block or override with its path. The conditions in effect are logged for every Tm/fold attribute and recorded in summary.json (inputs.hybridization_conditions). When a protocol declares them, they are also shown in the HTML report header and stored in the result .xlsx as custom document properties (hybridization:<attribute>).post_process is left out) follow the hybridization temperature: each Tm attribute keeps [temperature_c, temperature_c + 40] °C, the 40 °C width of the legacy window, since Tm under declared FISH conditions (e.g. 50% formamide) can fall tens of degrees below the legacy 50-90 °C. An attribute's own temperature_c wins; an override that changes salt, probe or formamide without its own temperature_c (such as the MERFISH design conditions above) keeps 50-90 °C. Conditions declared without temperature_c use the default 37 °C (37-77 °C), with a warning from uprobe run and uprobe validate. Without the block (or with an empty one) the filters stay at 50-90 °C. The chosen windows are logged.target_mapped_genesaligner: bowtiemax_mismatches: 2strand: sensealign_indexbowtiebowtie2uprobe validate -goligos (with aligner: bowtie and target: target_region split by a layout) searches each oligo's binding site on its own instead and counts a gene when every oligo has a site within max_mismatches on one of its transcripts; the _off_targets column gives the worst oligo's mismatches. Each entry is a layout part or a list of adjacent parts joined into one oligo, e.g. oligos: [left, right] (HCR v3) or oligos: [[arm5_site, arm3_site], primer_site] (a padlock's two arm sites and a primer). max_gap: <nt> also requires the sites to lie on one strand, not overlapping, within that distance of the longest oligo's site; without it, sites anywhere on the same transcript count. The HCR v3, STARmap and MiP-seq templates use oligos (without max_gap), so their genome needs bowtie in align_index. The longest oligo of a unit is searched first and the others keep only sites on the transcripts it hits, so short oligos (a 13-nt primer site has thousands of sites within 2 substitutions) stay cheap.mapped_genes attribute also writes <attribute>_off_targets to the result tables (.xlsx and _probes.tsv, next to the attribute): the genes other than the row's target that the sequence hits, each with its fewest mismatches, e.g. ACTG1:0, POTEF:2 (names only with bowtie2). Genes are named after the GTF's gene_name; a name that several gene ids share is written name(gene_id). The count includes the row's target once, however many gene ids carry its name: a copy of the gene on an assembly patch or alternate haplotype (Ensembl cDNA includes them, each with a gene id of its own) is the target, not a second gene, so target_mapped_genes <= 1 keeps a window that hits both. Genes that an expression table rules out go to <attribute>_unexpressed, copies of the target to <attribute>_copies, and with a table of transcripts <attribute>_hit_transcripts gives the transcripts each off-target gene was valued by (see below).summary.json name every exempt gene: per attribute, copy_genes, copies (each target's exempt copies with their shares) and final_copy_genes (those the final probes hit), and every copy that is not exempt (copies_not_exempt). A target whose windows give no k-mer keeps all its off-target genes and is reported (targets_without_sequence, warning copies_not_checked).protein_coding are not exempt, so they count against the SPDYE17 probes and appear under copies_not_exempt; members annotated as pseudogenes are exempt and appear in <attribute>_copies.