seqforge 2026.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- seqforge/__init__.py +16 -0
- seqforge/cli/__init__.py +38 -0
- seqforge/cli/__main__.py +8 -0
- seqforge/cli/_common.py +105 -0
- seqforge/cli/compose.py +119 -0
- seqforge/cli/eval.py +103 -0
- seqforge/cli/harvest.py +417 -0
- seqforge/cli/hook.py +247 -0
- seqforge/cli/io.py +502 -0
- seqforge/cli/kb.py +348 -0
- seqforge/cli/manifest.py +536 -0
- seqforge/cli/probe.py +43 -0
- seqforge/cli/processing.py +192 -0
- seqforge/cli/project.py +52 -0
- seqforge/cli/resolve.py +55 -0
- seqforge/cli/root.py +66 -0
- seqforge/cli/run.py +463 -0
- seqforge/cli/schema.py +41 -0
- seqforge/compose/__init__.py +28 -0
- seqforge/compose/core.py +515 -0
- seqforge/compose/gates.py +113 -0
- seqforge/compose/params.py +447 -0
- seqforge/e2e.py +1926 -0
- seqforge/evals/__init__.py +78 -0
- seqforge/evals/case.py +382 -0
- seqforge/evals/grade.py +300 -0
- seqforge/evals/run.py +420 -0
- seqforge/harvest/__init__.py +121 -0
- seqforge/harvest/extract.py +319 -0
- seqforge/harvest/fields.py +212 -0
- seqforge/harvest/normalize.py +537 -0
- seqforge/harvest/prep.py +41 -0
- seqforge/harvest/providers.py +321 -0
- seqforge/harvest/verify.py +251 -0
- seqforge/hooks/__init__.py +33 -0
- seqforge/hooks/guards.py +214 -0
- seqforge/io/__init__.py +61 -0
- seqforge/io/archive.py +450 -0
- seqforge/io/attributes.py +190 -0
- seqforge/io/biosample/attributes.json +6341 -0
- seqforge/io/efo/labels.json +55 -0
- seqforge/io/efo.py +138 -0
- seqforge/io/onlist.py +661 -0
- seqforge/io/onlists/3M-february-2018.codes.gz +0 -0
- seqforge/io/onlists/737K-arc-v1.codes.gz +0 -0
- seqforge/io/onlists/737K-august-2016.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls1-384.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls1.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls2-384.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls2.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls3-384.codes.gz +0 -0
- seqforge/io/onlists/bd-rhapsody-cls3.codes.gz +0 -0
- seqforge/io/onlists/index.json +74 -0
- seqforge/io/remote.py +659 -0
- seqforge/io/taxonomy.py +194 -0
- seqforge/kb/__init__.py +62 -0
- seqforge/kb/anchor.py +169 -0
- seqforge/kb/generate.py +147 -0
- seqforge/kb/loader.py +152 -0
- seqforge/kb/roundtrip.py +112 -0
- seqforge/kb/schema.py +422 -0
- seqforge/kb/specs/10x-3p-gex/spec.yaml +62 -0
- seqforge/kb/specs/10x-3p-gex-v2/README.md +41 -0
- seqforge/kb/specs/10x-3p-gex-v2/spec.yaml +83 -0
- seqforge/kb/specs/10x-3p-gex-v3/README.md +56 -0
- seqforge/kb/specs/10x-3p-gex-v3/spec.yaml +118 -0
- seqforge/kb/specs/10x-3p-gex-v3.1/README.md +56 -0
- seqforge/kb/specs/10x-3p-gex-v3.1/spec.yaml +124 -0
- seqforge/kb/specs/bd-rhapsody-wta/README.md +103 -0
- seqforge/kb/specs/bd-rhapsody-wta/spec.yaml +130 -0
- seqforge/kb/specs/bd-rhapsody-wta-enhanced/spec.yaml +99 -0
- seqforge/kb/specs/bd-rhapsody-wta-enhanced-v1/spec.yaml +93 -0
- seqforge/kb/specs/bd-rhapsody-wta-enhanced-v2/spec.yaml +81 -0
- seqforge/kb/specs/bulk-rnaseq-pe/README.md +35 -0
- seqforge/kb/specs/bulk-rnaseq-pe/spec.yaml +97 -0
- seqforge/kb/specs/splitseq/README.md +51 -0
- seqforge/kb/specs/splitseq/spec.yaml +157 -0
- seqforge/manifest/__init__.py +61 -0
- seqforge/manifest/fill.py +531 -0
- seqforge/manifest/hash.py +77 -0
- seqforge/manifest/instruct.py +114 -0
- seqforge/manifest/policy.py +409 -0
- seqforge/manifest/validate.py +274 -0
- seqforge/models/__init__.py +268 -0
- seqforge/models/assertion.py +68 -0
- seqforge/models/base.py +100 -0
- seqforge/models/blocker.py +71 -0
- seqforge/models/conflict.py +47 -0
- seqforge/models/dataset.py +320 -0
- seqforge/models/evidenced.py +54 -0
- seqforge/models/observation.py +157 -0
- seqforge/models/processing.py +231 -0
- seqforge/models/records.py +145 -0
- seqforge/models/resolve.py +216 -0
- seqforge/probe/__init__.py +46 -0
- seqforge/probe/core.py +232 -0
- seqforge/probe/signals.py +250 -0
- seqforge/probe/streaming.py +118 -0
- seqforge/project.py +177 -0
- seqforge/py.typed +0 -0
- seqforge/resolve/__init__.py +98 -0
- seqforge/resolve/assign.py +204 -0
- seqforge/resolve/cache.py +119 -0
- seqforge/resolve/confuse.py +215 -0
- seqforge/resolve/engine.py +646 -0
- seqforge/resolve/escalate.py +668 -0
- seqforge/resolve/evaluators.py +306 -0
- seqforge/resolve/geometry.py +89 -0
- seqforge/resolve/group.py +85 -0
- seqforge/resolve/records.py +550 -0
- seqforge/resolve/scoring.py +373 -0
- seqforge/resolve/window.py +206 -0
- seqforge/workflows/__init__.py +234 -0
- seqforge/workflows/cram.py +117 -0
- seqforge/workflows/h5ad.py +368 -0
- seqforge/workflows/map/star.smk +101 -0
- seqforge/workflows/map/starsolo.smk +360 -0
- seqforge/workflows/qc.py +157 -0
- seqforge/workspace.py +125 -0
- seqforge-2026.7.1.dist-info/METADATA +125 -0
- seqforge-2026.7.1.dist-info/RECORD +124 -0
- seqforge-2026.7.1.dist-info/WHEEL +4 -0
- seqforge-2026.7.1.dist-info/entry_points.txt +2 -0
- seqforge-2026.7.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
schema_version: 1
|
|
2
|
+
|
|
3
|
+
# An ABSTRACT FAMILY NODE, not a runnable chemistry. It has no `backend`: v2 and v3 parse reads
|
|
4
|
+
# differently (10 vs 12 bp UMI, different whitelists), so there is no single recipe to compile — descent
|
|
5
|
+
# always resolves to a leaf child. Its job is threefold: (1) group v2/v3/v3.1 so their pairwise
|
|
6
|
+
# `processing_divergent` confusable edges collapse to one `parent` link; (2) carry `children_decided_by`
|
|
7
|
+
# so the divergent-tie question knows the whitelist separates them; (3) be the family a prose/metadata
|
|
8
|
+
# prior names ("10x 3' gene expression") when it orders the descent. It is NEVER scored as a candidate
|
|
9
|
+
# (the scorer skips backend-less nodes) and is validated by the recognition self-test, not `kb roundtrip`.
|
|
10
|
+
|
|
11
|
+
identity:
|
|
12
|
+
id: 10x-3p-gex
|
|
13
|
+
version: family
|
|
14
|
+
name: "10x Chromium Single Cell 3' Gene Expression (family)"
|
|
15
|
+
aliases: ["10x 3'", "10x 3-prime", "Chromium 3' GEX", "10x Chromium 3' GEX", "SC3P", "single cell 3-prime gene expression"]
|
|
16
|
+
assay_ontology: ["EFO:0009922"] # 10x 3' GEX (the assay the whole family instantiates)
|
|
17
|
+
modality: rna
|
|
18
|
+
|
|
19
|
+
node_kind: family
|
|
20
|
+
# The children (v2 / v3 / v3.1) share 16 bp CB geometry and are told apart ONLY by their whitelist —
|
|
21
|
+
# so a divergent tie among them is decidable_by onlist, sourced here instead of a per-sibling edge.
|
|
22
|
+
children_decided_by: [onlist]
|
|
23
|
+
|
|
24
|
+
reads:
|
|
25
|
+
# The loose 10x 3' barcode read: 16 bp CB + a 10-12 bp UMI (26-28 bp total), covering v2 (26) and
|
|
26
|
+
# v3/v3.1 (28). min/max_len 26..28 is what keeps the family SEPARABLE from bulk: a long cDNA read
|
|
27
|
+
# (>= 40 bp) fails this gate, so the family never claims bulk/SPLiT-seq data.
|
|
28
|
+
- id: R1
|
|
29
|
+
seqspec_read_id: R1
|
|
30
|
+
file_hint: "_R1_"
|
|
31
|
+
strand: pos
|
|
32
|
+
min_len: 26
|
|
33
|
+
max_len: 28
|
|
34
|
+
elements:
|
|
35
|
+
- {type: barcode, name: CB, start: 0, end: 16, seqspec_region_type: barcode}
|
|
36
|
+
- {type: umi, name: UMI, start: 16, end: 28, seqspec_region_type: umi}
|
|
37
|
+
- id: R2
|
|
38
|
+
seqspec_read_id: R2
|
|
39
|
+
file_hint: "_R2_"
|
|
40
|
+
strand: pos
|
|
41
|
+
min_len: 25
|
|
42
|
+
max_len: null
|
|
43
|
+
elements:
|
|
44
|
+
- {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
|
|
45
|
+
|
|
46
|
+
onlists: {}
|
|
47
|
+
|
|
48
|
+
signature:
|
|
49
|
+
# Coarse recognition only — enough to admit any 10x 3' child at rungs 0-2 and to reject bulk (whose
|
|
50
|
+
# R1 is a long cDNA, not a 16 bp CB in a 26-28 bp read). NO exact segment_length: 26 and 28 both pass.
|
|
51
|
+
requires:
|
|
52
|
+
- {test: read_count, roles: 2}
|
|
53
|
+
- {test: has_segment, read: R1, start: 0, end: 16, kind: random}
|
|
54
|
+
supports:
|
|
55
|
+
- when: {test: distinct_ratio, read: R1, start: 0, end: 16, expect: low}
|
|
56
|
+
weight: 2.0 # the CB recurs across reads
|
|
57
|
+
- when: {test: distinct_ratio, read: R2, start: 0, end: 20, expect: high}
|
|
58
|
+
weight: 1.0 # cDNA is diverse
|
|
59
|
+
excludes: []
|
|
60
|
+
|
|
61
|
+
# no backend: abstract classifier. no parent: a root family.
|
|
62
|
+
confusable_with: []
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 10x Chromium Single Cell 3' Gene Expression v2
|
|
2
|
+
|
|
3
|
+
Droplet single-cell RNA-seq — the generation before v3. Every droplet holds one gel bead, which tags
|
|
4
|
+
all of a cell's transcripts with the same **cell barcode** and each individual mRNA molecule with a
|
|
5
|
+
unique **UMI**. That lets you count molecules per gene per cell.
|
|
6
|
+
|
|
7
|
+
## How it's read
|
|
8
|
+
|
|
9
|
+
Two reads come off the sequencer:
|
|
10
|
+
|
|
11
|
+
| read | length | what it holds |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| **R1** | 26 bp | a 16 bp **cell barcode** + a 10 bp **UMI** |
|
|
14
|
+
| **R2** | open-ended | the **cDNA** (the transcript itself), read sense to the mRNA |
|
|
15
|
+
|
|
16
|
+
The cell barcode is drawn from 10x's published `737K-august-2016` list (~737,000 barcodes).
|
|
17
|
+
|
|
18
|
+
## How seqforge tells v2 apart from v3
|
|
19
|
+
|
|
20
|
+
The only difference visible in the reads is **R1 length**: v2 is 16 + **10** = **26 bp**; v3 is
|
|
21
|
+
16 + **12** = **28 bp**. That 2 bp gap is decisive on its own, so seqforge separates v2 from v3 by
|
|
22
|
+
read geometry alone — before it ever consults a barcode list. The two are never confused. (Their
|
|
23
|
+
whitelists differ too — `737K-august-2016` vs `3M-february-2018` — but length settles it first.)
|
|
24
|
+
|
|
25
|
+
If a paper or database says "v2" while the reads are actually 28 bp, seqforge does **not** quietly
|
|
26
|
+
pick one. It surfaces the disagreement (metadata says 26 bp, the bytes say 28 bp) and hands it to a
|
|
27
|
+
human — the bytes decide what the data is.
|
|
28
|
+
|
|
29
|
+
## Gotchas
|
|
30
|
+
|
|
31
|
+
- **The barcode read can go missing on SRA.** `fasterq-dump` without `--include-technical` drops R1
|
|
32
|
+
entirely; seqforge blocks rather than guessing which file is which. Re-fetch with
|
|
33
|
+
`--include-technical`, or pull the submitter's original files.
|
|
34
|
+
- SRA rewrites read-name headers, so seqforge never trusts them when grouping files into samples.
|
|
35
|
+
|
|
36
|
+
## References
|
|
37
|
+
|
|
38
|
+
Read structure cross-checked against
|
|
39
|
+
**[scg_lib_structs — 10x Chromium 3'](https://teichlab.github.io/scg_lib_structs/methods_html/10xChromium3.html)**
|
|
40
|
+
(Teichmann Lab, CC-BY), which lays out the v2–v4 kits side by side. The exact, machine-readable
|
|
41
|
+
definition seqforge uses lives in this entry's `spec.yaml`.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
schema_version: 1
|
|
2
|
+
|
|
3
|
+
identity:
|
|
4
|
+
id: 10x-3p-gex-v2
|
|
5
|
+
version: "2"
|
|
6
|
+
name: "10x Chromium Single Cell 3' Gene Expression v2"
|
|
7
|
+
aliases: ["10x 3' v2", "Chromium 3' v2", "SC3Pv2", "single cell 3-prime v2"]
|
|
8
|
+
assay_ontology: ["EFO:0009899"] # 10x 3' v2 — verified against live EFO (EBI OLS), not memory
|
|
9
|
+
modality: rna
|
|
10
|
+
|
|
11
|
+
reads:
|
|
12
|
+
# R1 = the technical/barcode read. 26 bp = 16 CB + 10 UMI (the 2 bp shorter UMI vs v3's 28 bp).
|
|
13
|
+
# For a CANONICAL read the segment_length gate below separates v2 from v3 by that 26-vs-28. But
|
|
14
|
+
# max_len is null (an over-sequenced R1 is still v2), and when both accept an over-length read on
|
|
15
|
+
# geometry, the WHITELIST decides instead (v2 = 737K-august-2016) — see confusable_with.
|
|
16
|
+
- id: R1
|
|
17
|
+
seqspec_read_id: R1
|
|
18
|
+
file_hint: "_R1_"
|
|
19
|
+
strand: pos
|
|
20
|
+
min_len: 26
|
|
21
|
+
max_len: null
|
|
22
|
+
elements:
|
|
23
|
+
- {type: barcode, name: CB, start: 0, end: 16, onlist: cb_whitelist, seqspec_region_type: barcode}
|
|
24
|
+
- {type: umi, name: UMI, start: 16, end: 26, seqspec_region_type: umi}
|
|
25
|
+
- id: R2
|
|
26
|
+
seqspec_read_id: R2
|
|
27
|
+
file_hint: "_R2_"
|
|
28
|
+
strand: pos
|
|
29
|
+
min_len: 25
|
|
30
|
+
max_len: null
|
|
31
|
+
elements:
|
|
32
|
+
- {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
|
|
33
|
+
|
|
34
|
+
onlists:
|
|
35
|
+
cb_whitelist:
|
|
36
|
+
registry: 737K-august-2016 # ~737,280 x 16 bp, forward orientation (v2 whitelist)
|
|
37
|
+
role: cell_barcode
|
|
38
|
+
expected_orientation: forward
|
|
39
|
+
|
|
40
|
+
signature:
|
|
41
|
+
requires: # length separates v2 (26) from v3 (28) for canonical reads
|
|
42
|
+
- {test: read_count, roles: 2}
|
|
43
|
+
# length 26 exact for canonical reads; over_length_min 100 admits a full-length over-sequenced R1
|
|
44
|
+
# without loosening 26-vs-28 (a 28 bp read is < 100 -> not over-length -> still FAILs v2's exact
|
|
45
|
+
# 26), and a 60-94 bp cDNA/split-pool read stays exact-checked and rejected.
|
|
46
|
+
- {test: segment_length, read: R1, length: 26, tolerance: 0, over_length_min: 100}
|
|
47
|
+
- {test: has_segment, read: R1, start: 0, end: 16, kind: random}
|
|
48
|
+
- {test: has_segment, read: R1, start: 16, end: 26, kind: random}
|
|
49
|
+
supports:
|
|
50
|
+
- when: {test: onlist_hit_rate, read: R1, start: 0, end: 16, onlist: cb_whitelist, orientation: forward, min: 0.6}
|
|
51
|
+
weight: 5.0
|
|
52
|
+
- when: {test: distinct_ratio, read: R1, start: 0, end: 16, expect: low}
|
|
53
|
+
weight: 2.0
|
|
54
|
+
- when: {test: distinct_ratio, read: R1, start: 16, end: 26, expect: high}
|
|
55
|
+
weight: 1.0
|
|
56
|
+
- when: {test: distinct_ratio, read: R2, start: 0, end: 20, expect: high}
|
|
57
|
+
weight: 1.0 # cDNA is diverse -> the biological role is scored, not left at 0
|
|
58
|
+
excludes: []
|
|
59
|
+
|
|
60
|
+
backend:
|
|
61
|
+
module: map/starsolo
|
|
62
|
+
params:
|
|
63
|
+
soloType: CB_UMI_Simple
|
|
64
|
+
soloCBstart: 1
|
|
65
|
+
soloCBlen: 16
|
|
66
|
+
soloUMIstart: 17
|
|
67
|
+
soloUMIlen: 10 # 10 bp UMI (vs v3's 12) -> 26 bp R1
|
|
68
|
+
soloCBwhitelist: "{onlist:cb_whitelist}"
|
|
69
|
+
soloStrand: Forward
|
|
70
|
+
# 0 disables STARsolo's barcode-read-length check (default 1 FATALs on an over-length barcode
|
|
71
|
+
# read); CB/UMI still read from the fixed offsets above.
|
|
72
|
+
soloBarcodeReadLength: 0
|
|
73
|
+
# soloFeatures is NOT here, and that is the point. It says what to COUNT, and 10x 3' v3.1
|
|
74
|
+
# chemistry is byte-identical for cells and nuclei — what differs is the RNA population, a
|
|
75
|
+
# property of SAMPLE PREP. Filing it as chemistry cost a measured 40.7% of a nuclear library.
|
|
76
|
+
# It lives in the processing manifest now, where a user may instruct it.
|
|
77
|
+
|
|
78
|
+
# A child of the 10x-3p-gex family. Both former edges (to v3 and its byte-identical twin v3.1) were the
|
|
79
|
+
# OVER-LENGTH-R1 whitelist split among siblings — now declared once, in the parent's
|
|
80
|
+
# `children_decided_by: [onlist]`. So v2 declares no confusable_with of its own.
|
|
81
|
+
parent: 10x-3p-gex
|
|
82
|
+
|
|
83
|
+
confusable_with: []
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 10x Chromium Single Cell 3' Gene Expression v3 (and v3.1)
|
|
2
|
+
|
|
3
|
+
Droplet single-cell RNA-seq — the current-generation 3' kit. Every droplet holds one gel bead, which
|
|
4
|
+
tags all of a cell's transcripts with the same **cell barcode** and each mRNA molecule with a unique
|
|
5
|
+
**UMI**.
|
|
6
|
+
|
|
7
|
+
This page covers **both v3 and v3.1**. To a sequencer they are the same library, and seqforge
|
|
8
|
+
processes them identically — see [v3 vs v3.1](#v3-vs-v31) below.
|
|
9
|
+
|
|
10
|
+
## How it's read
|
|
11
|
+
|
|
12
|
+
| read | length | what it holds |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| **R1** | 28 bp | a 16 bp **cell barcode** + a 12 bp **UMI** |
|
|
15
|
+
| **R2** | open-ended | the **cDNA** (the transcript itself), read sense to the mRNA |
|
|
16
|
+
|
|
17
|
+
The cell barcode is drawn from 10x's `3M-february-2018` list (~6.8 million barcodes). Index reads
|
|
18
|
+
(I1/I2) may or may not be present; seqforge doesn't need them.
|
|
19
|
+
|
|
20
|
+
Names you'll see in the wild: "10x 3' v3", "Chromium 3' v3", "SC3Pv3", "single cell 3-prime v3",
|
|
21
|
+
"Next GEM 3' v3.1". Papers often write only "10x 3' v3" for a v3.1 run, or name the kit and not the
|
|
22
|
+
version — which changes nothing, as the next section explains.
|
|
23
|
+
|
|
24
|
+
## v3 vs v3.1
|
|
25
|
+
|
|
26
|
+
They are **identical to seqforge**: same read layout, same barcode list, same alignment settings. No
|
|
27
|
+
probe can tell them apart from the reads, and none needs to — so seqforge records both names and asks
|
|
28
|
+
you nothing. v3.1 exists as its own knowledge-base entry only so that "these two really are
|
|
29
|
+
identical" is something the test suite re-checks on every build, rather than a claim sitting in a
|
|
30
|
+
comment.
|
|
31
|
+
|
|
32
|
+
## How seqforge tells it apart from other chemistries
|
|
33
|
+
|
|
34
|
+
- **v2** — a 16 bp barcode + a **10** bp UMI makes a **26 bp** R1 (vs v3's 28 bp), on a different
|
|
35
|
+
list. The 2 bp length difference alone separates them.
|
|
36
|
+
- **Multiome (ARC) and GEM-X 3' v4** produce the same 28 bp / 16+12 layout — here **only the barcode
|
|
37
|
+
list** tells them apart, because each uses a different one. Geometry narrows to a family; the list
|
|
38
|
+
picks the exact member.
|
|
39
|
+
- **5' kits** share the barcode/UMI geometry but read the cDNA in the opposite direction. The reads
|
|
40
|
+
can't reveal that, so it takes metadata or a trial alignment, not geometry.
|
|
41
|
+
|
|
42
|
+
## Gotchas
|
|
43
|
+
|
|
44
|
+
- **The barcode read can go missing on SRA.** `fasterq-dump` without `--include-technical` drops the
|
|
45
|
+
28 bp R1; seqforge blocks rather than inventing a barcode read from a filename. Re-fetch with
|
|
46
|
+
`--include-technical`, or pull the submitter's original files.
|
|
47
|
+
- **A pre-trimmed upload** (R1 no longer a single fixed length) shifts the barcode offsets — seqforge
|
|
48
|
+
refuses rather than reading the barcode from the wrong place.
|
|
49
|
+
- SRA rewrites read-name headers, so seqforge never trusts them when grouping files into samples.
|
|
50
|
+
|
|
51
|
+
## References
|
|
52
|
+
|
|
53
|
+
Read structure cross-checked against
|
|
54
|
+
**[scg_lib_structs — 10x Chromium 3'](https://teichlab.github.io/scg_lib_structs/methods_html/10xChromium3.html)**
|
|
55
|
+
(Teichmann Lab, CC-BY). The exact, machine-readable definition seqforge uses lives in each entry's
|
|
56
|
+
`spec.yaml`.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
schema_version: 1
|
|
2
|
+
|
|
3
|
+
identity:
|
|
4
|
+
id: 10x-3p-gex-v3
|
|
5
|
+
version: "3"
|
|
6
|
+
name: "10x Chromium Single Cell 3' Gene Expression v3"
|
|
7
|
+
aliases: ["10x 3' v3", "Chromium 3' v3", "SC3Pv3", "single cell 3-prime v3"]
|
|
8
|
+
assay_ontology: ["EFO:0009922"] # 10x 3' v3 — verified against live EFO (EBI OLS)
|
|
9
|
+
modality: rna
|
|
10
|
+
|
|
11
|
+
reads:
|
|
12
|
+
# R1 = the technical/barcode read. 28 bp = 16 CB + 12 UMI. max_len is null, not 28: an
|
|
13
|
+
# over-sequenced R1 (e.g. 150 bp, CB+UMI in bp0-28, the rest junk) is a real 10x layout, and the
|
|
14
|
+
# segment_length over_length_min below is what still keeps 28 separable from v2's 26.
|
|
15
|
+
- id: R1
|
|
16
|
+
seqspec_read_id: R1
|
|
17
|
+
file_hint: "_R1_"
|
|
18
|
+
strand: pos
|
|
19
|
+
min_len: 28
|
|
20
|
+
max_len: null
|
|
21
|
+
elements:
|
|
22
|
+
- {type: barcode, name: CB, start: 0, end: 16, onlist: cb_whitelist, seqspec_region_type: barcode}
|
|
23
|
+
- {type: umi, name: UMI, start: 16, end: 28, seqspec_region_type: umi}
|
|
24
|
+
# R2 = cDNA (open-ended). soloStrand Forward: R2 is sense to mRNA for 3' kits.
|
|
25
|
+
- id: R2
|
|
26
|
+
seqspec_read_id: R2
|
|
27
|
+
file_hint: "_R2_"
|
|
28
|
+
strand: pos
|
|
29
|
+
min_len: 25
|
|
30
|
+
max_len: null
|
|
31
|
+
elements:
|
|
32
|
+
- {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
|
|
33
|
+
|
|
34
|
+
onlists:
|
|
35
|
+
cb_whitelist: # used by backend + a supports test
|
|
36
|
+
registry: 3M-february-2018 # ~6,794,880 x 16 bp, forward orientation
|
|
37
|
+
role: cell_barcode
|
|
38
|
+
expected_orientation: forward
|
|
39
|
+
arc_v1_probe: # probe-only, used ONLY by the exclude anti-gate
|
|
40
|
+
registry: 737K-arc-v1 # Multiome (ARC) GEX list
|
|
41
|
+
role: cell_barcode
|
|
42
|
+
expected_orientation: forward
|
|
43
|
+
|
|
44
|
+
signature:
|
|
45
|
+
requires: # hard structural gates, rungs 0-2 friendly (NO distinct_ratio, NO onlist)
|
|
46
|
+
- {test: read_count, roles: 2}
|
|
47
|
+
# length 28 stays exact for canonical reads; over_length_min 100 admits a full-length
|
|
48
|
+
# over-sequenced R1 (>=100 bp: the run read length) without loosening 26-vs-28. A shorter
|
|
49
|
+
# non-canonical read (a 60-94 bp cDNA or split-pool barcode) is below it, so it is still
|
|
50
|
+
# exact-checked and rejected -> v3 stays un-confusable with cDNA-only chemistries at rungs 0-2.
|
|
51
|
+
- {test: segment_length, read: R1, length: 28, tolerance: 0, over_length_min: 100}
|
|
52
|
+
- {test: has_segment, read: R1, start: 0, end: 16, kind: random}
|
|
53
|
+
- {test: has_segment, read: R1, start: 16, end: 28, kind: random}
|
|
54
|
+
supports: # additive evidence (rung 3+)
|
|
55
|
+
- when: {test: onlist_hit_rate, read: R1, start: 0, end: 16, onlist: cb_whitelist, orientation: forward, min: 0.6}
|
|
56
|
+
weight: 5.0
|
|
57
|
+
- when: {test: distinct_ratio, read: R1, start: 0, end: 16, expect: low}
|
|
58
|
+
weight: 2.0 # CB recurs -> low distinct ratio
|
|
59
|
+
- when: {test: distinct_ratio, read: R1, start: 16, end: 28, expect: high}
|
|
60
|
+
weight: 1.0 # UMI ~ unique -> high distinct ratio
|
|
61
|
+
- when: {test: distinct_ratio, read: R2, start: 0, end: 20, expect: high}
|
|
62
|
+
weight: 1.0 # cDNA is diverse -> the biological role is scored, not left at 0
|
|
63
|
+
- when: {test: header_index, present: true}
|
|
64
|
+
weight: 0.2
|
|
65
|
+
excludes: # anti-gate: if the Multiome list hits, it's not 3' GEX
|
|
66
|
+
- {test: onlist_hit_rate, read: R1, start: 0, end: 16, onlist: arc_v1_probe, orientation: forward, min: 0.6}
|
|
67
|
+
|
|
68
|
+
backend:
|
|
69
|
+
module: map/starsolo
|
|
70
|
+
params: # chemistry-defining MINIMUM (CellRanger-parity knobs come from processing policy)
|
|
71
|
+
soloType: CB_UMI_Simple
|
|
72
|
+
soloCBstart: 1
|
|
73
|
+
soloCBlen: 16
|
|
74
|
+
soloUMIstart: 17
|
|
75
|
+
soloUMIlen: 12
|
|
76
|
+
soloCBwhitelist: "{onlist:cb_whitelist}"
|
|
77
|
+
soloStrand: Forward
|
|
78
|
+
# 0 disables STARsolo's barcode-read-length check, which by default (1) FATALs when the barcode
|
|
79
|
+
# read is longer than CB+UMI. An over-sequenced R1 (150 bp) needs this; CB/UMI are still read from
|
|
80
|
+
# the fixed offsets above. Kept identical in v3/v3.1 so `backend_identical` holds.
|
|
81
|
+
soloBarcodeReadLength: 0
|
|
82
|
+
# soloFeatures is NOT here, and that is the point. It says what to COUNT, and 10x 3' v3.1
|
|
83
|
+
# chemistry is byte-identical for cells and nuclei — what differs is the RNA population, a
|
|
84
|
+
# property of SAMPLE PREP. Filing it as chemistry cost a measured 40.7% of a nuclear library.
|
|
85
|
+
# It lives in the processing manifest now, where a user may instruct it.
|
|
86
|
+
|
|
87
|
+
# A child of the 10x-3p-gex family. The v2 sibling edge (over-length whitelist split) now lives in the
|
|
88
|
+
# parent's `children_decided_by: [onlist]`, so it is deleted here — the tree declares it once.
|
|
89
|
+
parent: 10x-3p-gex
|
|
90
|
+
|
|
91
|
+
confusable_with:
|
|
92
|
+
- id: 10x-3p-gex-v3.1
|
|
93
|
+
relationship: processing_equivalent
|
|
94
|
+
distinguishable_by: [none]
|
|
95
|
+
note: >
|
|
96
|
+
Identical 16+12=28 bp geometry, identical 3M-february-2018 whitelist, byte-identical STARsolo
|
|
97
|
+
params -> §12 benign: the resolver records both ids and asks 0 questions. KEPT as an explicit
|
|
98
|
+
edge (not collapsed into the parent): backend-identity is a byte fact the §2.4 biconditional
|
|
99
|
+
verifies pairwise, distinct from the divergent sibling relationship the tree carries.
|
|
100
|
+
- id: 10x-multiome-gex
|
|
101
|
+
relationship: processing_divergent
|
|
102
|
+
distinguishable_by: [onlist]
|
|
103
|
+
note: >
|
|
104
|
+
Same 28 bp geometry, but Multiome (ARC) GEX uses 737K-arc-v1, not 3M-february-2018. Rung-3
|
|
105
|
+
onlist check separates them; backend params differ only in soloCBwhitelist.
|
|
106
|
+
- id: 10x-gemx-3p-v4
|
|
107
|
+
relationship: processing_divergent
|
|
108
|
+
distinguishable_by: [onlist]
|
|
109
|
+
note: >
|
|
110
|
+
REQUIRED declaration: GEM-X 3' v4 shares the 28 bp / 16+12 geometry and is separable only by
|
|
111
|
+
its newer whitelist. Without this entry the flagship fails the CI under-declaration check.
|
|
112
|
+
- id: 10x-5p-gex
|
|
113
|
+
relationship: processing_divergent
|
|
114
|
+
distinguishable_by: [metadata, alignment]
|
|
115
|
+
note: >
|
|
116
|
+
5' chemistry reads antisense cDNA -> soloStrand Reverse. Read-undecidable when geometry and
|
|
117
|
+
whitelist coincide; metadata (rung 0) or alignment (rung 6) resolves it.
|
|
118
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 10x Chromium Single Cell 3' Gene Expression v3 (and v3.1)
|
|
2
|
+
|
|
3
|
+
Droplet single-cell RNA-seq — the current-generation 3' kit. Every droplet holds one gel bead, which
|
|
4
|
+
tags all of a cell's transcripts with the same **cell barcode** and each mRNA molecule with a unique
|
|
5
|
+
**UMI**.
|
|
6
|
+
|
|
7
|
+
This page covers **both v3 and v3.1**. To a sequencer they are the same library, and seqforge
|
|
8
|
+
processes them identically — see [v3 vs v3.1](#v3-vs-v31) below.
|
|
9
|
+
|
|
10
|
+
## How it's read
|
|
11
|
+
|
|
12
|
+
| read | length | what it holds |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| **R1** | 28 bp | a 16 bp **cell barcode** + a 12 bp **UMI** |
|
|
15
|
+
| **R2** | open-ended | the **cDNA** (the transcript itself), read sense to the mRNA |
|
|
16
|
+
|
|
17
|
+
The cell barcode is drawn from 10x's `3M-february-2018` list (~6.8 million barcodes). Index reads
|
|
18
|
+
(I1/I2) may or may not be present; seqforge doesn't need them.
|
|
19
|
+
|
|
20
|
+
Names you'll see in the wild: "10x 3' v3", "Chromium 3' v3", "SC3Pv3", "single cell 3-prime v3",
|
|
21
|
+
"Next GEM 3' v3.1". Papers often write only "10x 3' v3" for a v3.1 run, or name the kit and not the
|
|
22
|
+
version — which changes nothing, as the next section explains.
|
|
23
|
+
|
|
24
|
+
## v3 vs v3.1
|
|
25
|
+
|
|
26
|
+
They are **identical to seqforge**: same read layout, same barcode list, same alignment settings. No
|
|
27
|
+
probe can tell them apart from the reads, and none needs to — so seqforge records both names and asks
|
|
28
|
+
you nothing. v3.1 exists as its own knowledge-base entry only so that "these two really are
|
|
29
|
+
identical" is something the test suite re-checks on every build, rather than a claim sitting in a
|
|
30
|
+
comment.
|
|
31
|
+
|
|
32
|
+
## How seqforge tells it apart from other chemistries
|
|
33
|
+
|
|
34
|
+
- **v2** — a 16 bp barcode + a **10** bp UMI makes a **26 bp** R1 (vs v3's 28 bp), on a different
|
|
35
|
+
list. The 2 bp length difference alone separates them.
|
|
36
|
+
- **Multiome (ARC) and GEM-X 3' v4** produce the same 28 bp / 16+12 layout — here **only the barcode
|
|
37
|
+
list** tells them apart, because each uses a different one. Geometry narrows to a family; the list
|
|
38
|
+
picks the exact member.
|
|
39
|
+
- **5' kits** share the barcode/UMI geometry but read the cDNA in the opposite direction. The reads
|
|
40
|
+
can't reveal that, so it takes metadata or a trial alignment, not geometry.
|
|
41
|
+
|
|
42
|
+
## Gotchas
|
|
43
|
+
|
|
44
|
+
- **The barcode read can go missing on SRA.** `fasterq-dump` without `--include-technical` drops the
|
|
45
|
+
28 bp R1; seqforge blocks rather than inventing a barcode read from a filename. Re-fetch with
|
|
46
|
+
`--include-technical`, or pull the submitter's original files.
|
|
47
|
+
- **A pre-trimmed upload** (R1 no longer a single fixed length) shifts the barcode offsets — seqforge
|
|
48
|
+
refuses rather than reading the barcode from the wrong place.
|
|
49
|
+
- SRA rewrites read-name headers, so seqforge never trusts them when grouping files into samples.
|
|
50
|
+
|
|
51
|
+
## References
|
|
52
|
+
|
|
53
|
+
Read structure cross-checked against
|
|
54
|
+
**[scg_lib_structs — 10x Chromium 3'](https://teichlab.github.io/scg_lib_structs/methods_html/10xChromium3.html)**
|
|
55
|
+
(Teichmann Lab, CC-BY). The exact, machine-readable definition seqforge uses lives in each entry's
|
|
56
|
+
`spec.yaml`.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
schema_version: 1
|
|
2
|
+
|
|
3
|
+
# The §12 benign twin of 10x-3p-gex-v3, and the ONLY reason it is a separate file: `backend_identical`
|
|
4
|
+
# is a computed predicate, and a predicate cannot be computed about a spec that does not exist. v3
|
|
5
|
+
# declared `processing_equivalent: 10x-3p-gex-v3.1` from the start, but the twin was never written, so
|
|
6
|
+
# the flagship example of the rule the whole resolver is built around was the one pair CI could not
|
|
7
|
+
# check. Everything below except `identity` is byte-for-byte v3 — that IS the assertion.
|
|
8
|
+
#
|
|
9
|
+
# Do not "simplify" this by generating it from v3 at load time. The point is that two independently
|
|
10
|
+
# authored specs are compared and found identical; deriving one from the other would make
|
|
11
|
+
# `backend_identical(v3, v3.1)` true by construction and prove nothing.
|
|
12
|
+
|
|
13
|
+
identity:
|
|
14
|
+
id: 10x-3p-gex-v3.1
|
|
15
|
+
version: "3.1"
|
|
16
|
+
name: "10x Chromium Single Cell 3' Gene Expression v3.1"
|
|
17
|
+
aliases: ["10x 3' v3.1", "Chromium 3' v3.1", "SC3Pv3.1", "single cell 3-prime v3.1", "Next GEM 3' v3.1"]
|
|
18
|
+
assay_ontology: ["EFO:0022980"] # 10x 3' v3.1 — verified against live EFO (EBI OLS), FLAG-1
|
|
19
|
+
modality: rna
|
|
20
|
+
|
|
21
|
+
reads:
|
|
22
|
+
# Identical to v3: 28 bp = 16 CB + 12 UMI (max_len null: an over-sequenced R1 is still v3.1). This
|
|
23
|
+
# identity is the whole point — the probe cannot separate v3 from v3.1, and §12 says it does not
|
|
24
|
+
# need to; the over_length_min below keeps 28 separable from v2's 26 either way.
|
|
25
|
+
- id: R1
|
|
26
|
+
seqspec_read_id: R1
|
|
27
|
+
file_hint: "_R1_"
|
|
28
|
+
strand: pos
|
|
29
|
+
min_len: 28
|
|
30
|
+
max_len: null
|
|
31
|
+
elements:
|
|
32
|
+
- {type: barcode, name: CB, start: 0, end: 16, onlist: cb_whitelist, seqspec_region_type: barcode}
|
|
33
|
+
- {type: umi, name: UMI, start: 16, end: 28, seqspec_region_type: umi}
|
|
34
|
+
- id: R2
|
|
35
|
+
seqspec_read_id: R2
|
|
36
|
+
file_hint: "_R2_"
|
|
37
|
+
strand: pos
|
|
38
|
+
min_len: 25
|
|
39
|
+
max_len: null
|
|
40
|
+
elements:
|
|
41
|
+
- {type: cdna, name: cdna, start: 0, end: null, seqspec_region_type: cdna}
|
|
42
|
+
|
|
43
|
+
onlists:
|
|
44
|
+
cb_whitelist: # the SAME list as v3 — this is why no onlist check can separate them
|
|
45
|
+
registry: 3M-february-2018 # ~6,794,880 x 16 bp, forward orientation
|
|
46
|
+
role: cell_barcode
|
|
47
|
+
expected_orientation: forward
|
|
48
|
+
arc_v1_probe: # probe-only, used ONLY by the exclude anti-gate
|
|
49
|
+
registry: 737K-arc-v1 # Multiome (ARC) GEX list
|
|
50
|
+
role: cell_barcode
|
|
51
|
+
expected_orientation: forward
|
|
52
|
+
|
|
53
|
+
signature:
|
|
54
|
+
requires:
|
|
55
|
+
- {test: read_count, roles: 2}
|
|
56
|
+
# length 28 exact for canonical reads; over_length_min 100 admits a full-length over-sequenced
|
|
57
|
+
# R1 (>=100 bp) while a 60-94 bp cDNA/split-pool read stays exact-checked and rejected (see v3).
|
|
58
|
+
- {test: segment_length, read: R1, length: 28, tolerance: 0, over_length_min: 100}
|
|
59
|
+
- {test: has_segment, read: R1, start: 0, end: 16, kind: random}
|
|
60
|
+
- {test: has_segment, read: R1, start: 16, end: 28, kind: random}
|
|
61
|
+
supports:
|
|
62
|
+
- when: {test: onlist_hit_rate, read: R1, start: 0, end: 16, onlist: cb_whitelist, orientation: forward, min: 0.6}
|
|
63
|
+
weight: 5.0
|
|
64
|
+
- when: {test: distinct_ratio, read: R1, start: 0, end: 16, expect: low}
|
|
65
|
+
weight: 2.0
|
|
66
|
+
- when: {test: distinct_ratio, read: R1, start: 16, end: 28, expect: high}
|
|
67
|
+
weight: 1.0
|
|
68
|
+
- when: {test: distinct_ratio, read: R2, start: 0, end: 20, expect: high}
|
|
69
|
+
weight: 1.0
|
|
70
|
+
- when: {test: header_index, present: true}
|
|
71
|
+
weight: 0.2
|
|
72
|
+
excludes:
|
|
73
|
+
- {test: onlist_hit_rate, read: R1, start: 0, end: 16, onlist: arc_v1_probe, orientation: forward, min: 0.6}
|
|
74
|
+
|
|
75
|
+
backend:
|
|
76
|
+
module: map/starsolo
|
|
77
|
+
params: # byte-identical to v3 -> backend_identical -> §12 benign
|
|
78
|
+
soloType: CB_UMI_Simple
|
|
79
|
+
soloCBstart: 1
|
|
80
|
+
soloCBlen: 16
|
|
81
|
+
soloUMIstart: 17
|
|
82
|
+
soloUMIlen: 12
|
|
83
|
+
soloCBwhitelist: "{onlist:cb_whitelist}"
|
|
84
|
+
soloStrand: Forward
|
|
85
|
+
# 0 disables STARsolo's barcode-read-length check (default 1 FATALs on an over-length barcode
|
|
86
|
+
# read); byte-identical to v3's, which is what keeps `backend_identical` true.
|
|
87
|
+
soloBarcodeReadLength: 0
|
|
88
|
+
# soloFeatures is NOT here, and that is the point. It says what to COUNT, and 10x 3' v3.1
|
|
89
|
+
# chemistry is byte-identical for cells and nuclei — what differs is the RNA population, a
|
|
90
|
+
# property of SAMPLE PREP. Filing it as chemistry cost a measured 40.7% of a nuclear library.
|
|
91
|
+
# It lives in the processing manifest now, where a user may instruct it.
|
|
92
|
+
|
|
93
|
+
# A child of the 10x-3p-gex family; the v2 sibling edge now lives in the parent's children_decided_by.
|
|
94
|
+
parent: 10x-3p-gex
|
|
95
|
+
|
|
96
|
+
confusable_with:
|
|
97
|
+
- id: 10x-3p-gex-v3
|
|
98
|
+
relationship: processing_equivalent
|
|
99
|
+
distinguishable_by: [none]
|
|
100
|
+
note: >
|
|
101
|
+
The back-edge of v3's declaration. Identical 16+12=28 bp geometry, identical 3M-february-2018
|
|
102
|
+
whitelist, byte-identical STARsolo params -> §12 benign: the resolver records both ids and asks
|
|
103
|
+
0 questions. Declared on both sides so the relation reads as symmetric in the file, which is
|
|
104
|
+
what a human checks; the CI biconditional takes the union of both directions regardless.
|
|
105
|
+
- id: 10x-multiome-gex
|
|
106
|
+
relationship: processing_divergent
|
|
107
|
+
distinguishable_by: [onlist]
|
|
108
|
+
note: >
|
|
109
|
+
Same 28 bp geometry, but Multiome (ARC) GEX uses 737K-arc-v1, not 3M-february-2018. Rung-3
|
|
110
|
+
onlist check separates them; backend params differ only in soloCBwhitelist.
|
|
111
|
+
- id: 10x-gemx-3p-v4
|
|
112
|
+
relationship: processing_divergent
|
|
113
|
+
distinguishable_by: [onlist]
|
|
114
|
+
note: >
|
|
115
|
+
REQUIRED declaration: GEM-X 3' v4 shares the 28 bp / 16+12 geometry and is separable only by
|
|
116
|
+
its newer whitelist. v3.1 inherits every one of v3's divergent confusables — it is the same
|
|
117
|
+
chemistry to the probe, so it is confusable with exactly the same things.
|
|
118
|
+
- id: 10x-5p-gex
|
|
119
|
+
relationship: processing_divergent
|
|
120
|
+
distinguishable_by: [metadata, alignment]
|
|
121
|
+
note: >
|
|
122
|
+
5' chemistry reads antisense cDNA -> soloStrand Reverse. Read-undecidable when geometry and
|
|
123
|
+
whitelist coincide; metadata (rung 0) or alignment (rung 6) resolves it.
|
|
124
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# BD Rhapsody WTA (Whole Transcriptome Analysis)
|
|
2
|
+
|
|
3
|
+
BD Biosciences' droplet-free single-cell 3' RNA-seq. Cells are captured in a microwell plate and
|
|
4
|
+
tagged with a **cell label** built from three barcode blocks on the capture bead, plus a **UMI** on
|
|
5
|
+
each molecule.
|
|
6
|
+
|
|
7
|
+
BD ships this chemistry in two bead generations, and seqforge recognizes and compiles **both**
|
|
8
|
+
automatically, from the reads. The **original fixed-position bead** is described first; the 2022
|
|
9
|
+
**[Enhanced bead](#enhanced-beads-2022)** — with its variable-length diversity insert — is described
|
|
10
|
+
below. seqforge tells the two apart from the bytes (their linker structure differs), so you never have
|
|
11
|
+
to declare which bead a dataset used.
|
|
12
|
+
|
|
13
|
+
## How it's read
|
|
14
|
+
|
|
15
|
+
Two reads:
|
|
16
|
+
|
|
17
|
+
- **R2 = the cDNA** (the transcript), read sense to the mRNA.
|
|
18
|
+
- **R1 = the bead read**: three 9 bp cell-label blocks separated by two fixed linker sequences, then
|
|
19
|
+
an 8 bp UMI.
|
|
20
|
+
|
|
21
|
+
| segment on R1 | length | position |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| cell label 1 | 9 bp | 0–9 |
|
|
24
|
+
| linker 1 (`ACTGGCCTGCGA`) | 12 bp | 9–21 |
|
|
25
|
+
| cell label 2 | 9 bp | 21–30 |
|
|
26
|
+
| linker 2 (`GGTAGCGGTGACA`) | 13 bp | 30–43 |
|
|
27
|
+
| cell label 3 | 9 bp | 43–52 |
|
|
28
|
+
| UMI | 8 bp | 52–60 |
|
|
29
|
+
|
|
30
|
+
Each cell-label block is drawn from its own published list of **97** 9-mers, so there are 97³ ≈
|
|
31
|
+
913,000 possible cell labels. R1 is often sequenced past position 60 into poly(T)/cDNA; those extra
|
|
32
|
+
bases are ignored.
|
|
33
|
+
|
|
34
|
+
## How seqforge tells it apart
|
|
35
|
+
|
|
36
|
+
The **two fixed linker sequences at known positions** are the signature — no other supported
|
|
37
|
+
chemistry has them. Each cell-label block also matches its own barcode list, which both confirms the
|
|
38
|
+
call and separates BD Rhapsody from a plain bulk paired-end library (bulk has no barcode list to
|
|
39
|
+
match).
|
|
40
|
+
|
|
41
|
+
## Strand
|
|
42
|
+
|
|
43
|
+
The bead's capture oligo ends in poly(dT), which primes off the mRNA poly(A) tail, so R2 reads the
|
|
44
|
+
cDNA **sense** to the transcript — a standard 3' poly-dT library, like 10x 3'. Getting this backwards
|
|
45
|
+
would leave most reads uncounted while the aligner still exits cleanly, so seqforge derives it from
|
|
46
|
+
the bead chemistry rather than guessing.
|
|
47
|
+
|
|
48
|
+
## Enhanced beads (2022)
|
|
49
|
+
|
|
50
|
+
In 2022 BD introduced **Enhanced Cell Capture Beads**, the current chemistry. The cell labels work the
|
|
51
|
+
same way — three 9 bp blocks read off the bead — but two things change:
|
|
52
|
+
|
|
53
|
+
- A short **diversity insert** of 0–3 bp (one of *nothing*, `A`, `GT`, or `TCA`) is added at the very
|
|
54
|
+
start of Read 1, to stagger the reads and cut the amount of PhiX needed.
|
|
55
|
+
- The two linkers shrink to **`GTGA`** and **`GACA`** (4 bp each), from v1's 12 and 13 bp.
|
|
56
|
+
|
|
57
|
+
So Enhanced Read 1 reads:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
[0–3 bp insert] [CLS1 · 9] GTGA [CLS2 · 9] GACA [CLS3 · 9] [UMI · 8]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
There are two sub-versions, differing only in the cell-label lists: **96** or **384** sequences per
|
|
64
|
+
block (the 384-list "Enhanced v2" allows more cell labels). The read layout is otherwise identical.
|
|
65
|
+
|
|
66
|
+
### How seqforge handles the Enhanced bead
|
|
67
|
+
|
|
68
|
+
seqforge recognizes and compiles the Enhanced bead **automatically** — you do not tell it the bead
|
|
69
|
+
generation, and you do not pre-trim the diversity insert.
|
|
70
|
+
|
|
71
|
+
- **Recognition.** The diversity insert shifts every barcode by a different amount from one read to the
|
|
72
|
+
next, so the positions are not fixed. seqforge finds the `GTGA…GACA` linker frame in each read
|
|
73
|
+
(tolerating the 0–3 bp stagger and sequencing error), then reads the cell-label and UMI blocks
|
|
74
|
+
relative to it. The presence of that frame is what separates Enhanced from the original bead, whose
|
|
75
|
+
longer `ACTGGCCTGCGA`/`GGTAGCGGTGACA` linkers sit at fixed positions instead.
|
|
76
|
+
- **96 vs 384.** The two Enhanced sub-versions have identical read layout and differ only in which
|
|
77
|
+
cell-label list their blocks match, so seqforge picks the one the barcodes actually hit — exactly how
|
|
78
|
+
it tells 10x v2 from v3. The 96-plex bead reuses the original bead's cell-label lists; the 384-plex
|
|
79
|
+
lists ship with seqforge too.
|
|
80
|
+
- **Compilation.** seqforge maps Enhanced reads with STARsolo's adapter anchor — no pre-trimming — and
|
|
81
|
+
derives the exact settings STAR's author endorsed in
|
|
82
|
+
[issue #1607](https://github.com/alexdobin/STAR/issues/1607) straight from the read layout:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
STAR --soloType CB_UMI_Complex \
|
|
86
|
+
--soloAdapterSequence NNNNNNNNNGTGANNNNNNNNNGACA \
|
|
87
|
+
--soloCBposition 2_0_2_8 2_13_2_21 3_1_3_9 \
|
|
88
|
+
--soloUMIposition 3_10_3_17 \
|
|
89
|
+
--soloCBwhitelist BD_CLS1.txt BD_CLS2.txt BD_CLS3.txt
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The adapter `NNNNNNNNNGTGANNNNNNNNNGACA` is `CLS1(9) + GTGA + CLS2(9) + GACA`; STARsolo finds it in
|
|
93
|
+
each read and reads the barcodes relative to it, so the variable insert at the front takes care of
|
|
94
|
+
itself. For a reproducible reference workflow spanning every bead generation, see
|
|
95
|
+
**[rhapsodist](https://github.com/imallona/rhapsodist)**.
|
|
96
|
+
|
|
97
|
+
## References
|
|
98
|
+
|
|
99
|
+
Read structure, linker sequences, and barcode lists pinned from
|
|
100
|
+
**[scg_lib_structs — BD Rhapsody](https://teichlab.github.io/scg_lib_structs/methods_html/BD_Rhapsody.html)**
|
|
101
|
+
(Teichmann Lab, CC-BY), cross-checked against the seqspec `bd_rhapsody_v1` example and the STARsolo
|
|
102
|
+
maintainer's endorsed settings. The exact, machine-readable definition seqforge uses lives in this
|
|
103
|
+
entry's `spec.yaml`.
|