orfdata 0.7.0__tar.gz → 0.9.0__tar.gz
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.
- {orfdata-0.7.0 → orfdata-0.9.0}/MANIFEST.in +1 -0
- {orfdata-0.7.0/orfdata.egg-info → orfdata-0.9.0}/PKG-INFO +1 -1
- {orfdata-0.7.0 → orfdata-0.9.0}/docs/measurements.md +20 -1
- {orfdata-0.7.0 → orfdata-0.9.0}/docs/pfizer-protein.md +5 -2
- orfdata-0.9.0/docs/storage.md +45 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/dataset.py +157 -59
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/dataset_cli.py +5 -1
- orfdata-0.9.0/orfdata/version.py +1 -0
- {orfdata-0.7.0 → orfdata-0.9.0/orfdata.egg-info}/PKG-INFO +1 -1
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata.egg-info/SOURCES.txt +12 -1
- orfdata-0.9.0/tests/data/storage/README.md +15 -0
- orfdata-0.9.0/tests/data/storage/generate.py +74 -0
- orfdata-0.9.0/tests/data/storage/v1.json +582 -0
- orfdata-0.9.0/tests/data/storage/v1.sqlite +0 -0
- orfdata-0.9.0/tests/data/storage/v2.json +584 -0
- orfdata-0.9.0/tests/data/storage/v2.sqlite +0 -0
- orfdata-0.9.0/tests/data/storage/v3.json +613 -0
- orfdata-0.9.0/tests/data/storage/v3.sqlite +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/test_pairing.py +4 -8
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/test_reference_mapping.py +2 -6
- orfdata-0.9.0/tests/test_selectors.py +130 -0
- orfdata-0.9.0/tests/test_storage.py +76 -0
- orfdata-0.7.0/orfdata/version.py +0 -1
- {orfdata-0.7.0 → orfdata-0.9.0}/LICENSE +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/README.md +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/deploy.sh +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/docs/copy-yeast.md +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/docs/design.md +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/docs/pairing.md +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/docs/reference.md +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/docs/source-references.md +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/docs/species.md +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/lint.sh +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/__init__.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/build.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/catalog.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/cli.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/data/datasets.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/data/orf-evidence/AUTHOR-LICENSE.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/data/orf-evidence/NOTICE.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/data/orf-evidence/catalog.json.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/data/orf-evidence/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/data/uorf-evidence/AUTHOR-LICENSE.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/data/uorf-evidence/NOTICE.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/data/uorf-evidence/catalog.json.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/data/uorf-evidence/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/datasets.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/initiation_context.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/measurements.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/model.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/pairing.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/reference.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/reference_mapping.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/sequence.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/sources/__init__.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/sources/copy.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata/sources/pfizer.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata.egg-info/dependency_links.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata.egg-info/entry_points.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata.egg-info/requires.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/orfdata.egg-info/top_level.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/pyproject.toml +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/setup.cfg +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/test.sh +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/conftest.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/GSM1905474.soft +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/GSM1905474_KL9_fpkm.txt.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/GSM1905476.soft +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/GSM1905476_KL10_fpkm.txt.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/GSM1905478.soft +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/GSM1905478_KL11_fpkm.txt.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/GSM1905479.soft +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/GSM1905479_KL12_fpkm.txt.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/NOTICE.md +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/expected.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/geo-series.soft +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/methods.pdf +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/paper.xml +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/pmc-metadata.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/qconcat.fasta +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/copy/workbook.xlsx +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/NOTICE.md +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/dog/expected.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/dog/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/dog/protein.tsv +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/dog/search.fasta +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/human/expected.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/human/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/human/protein.tsv +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/human/search.fasta +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/mouse/expected.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/mouse/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/mouse/protein.tsv +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/pfizer/mouse/search.fasta +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/references/README.md +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/references/canis_lupus_familiaris.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/references/context-keys-v0.4.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/references/homo_sapiens.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/references/mus_musculus.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/data/references/saccharomyces_cerevisiae.json +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/test_copy.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/test_cross_reference_pairing.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/test_initiation_context.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/test_measurements.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/test_pfizer.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/test_reference_context.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/test_reference_orf.py +0 -0
- {orfdata-0.7.0 → orfdata-0.9.0}/tests/test_uorf_catalog.py +0 -0
|
@@ -51,6 +51,24 @@ For native sources with unreported genomic metadata, use explicit
|
|
|
51
51
|
[source references and mapping views](source-references.md) to retain the original
|
|
52
52
|
observation alongside its mapping to a PyEnsembl target.
|
|
53
53
|
|
|
54
|
+
Choose a native series explicitly when a source supplies several units:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
values = dataset.measurements(quantity="protein_abundance", unit="iBAQ", scale="linear")
|
|
58
|
+
dataset.export_parquet("ibaq.parquet", unit="iBAQ", scale="linear")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`unit`, `scale` and `log_base` also work with `mapped_measurements` and existing
|
|
62
|
+
storage v1-v4 releases. Units are exact and case-sensitive. Selecting
|
|
63
|
+
`scale="log", log_base=2` returns values already on that scale; nothing is transformed.
|
|
64
|
+
An unavailable series returns no observations. Contradictory linear/log requests
|
|
65
|
+
raise an error. Omitting selectors preserves all native series.
|
|
66
|
+
|
|
67
|
+
For example, Pfizer iBAQ and rIBAQ are two series from the same source rows.
|
|
68
|
+
Selecting or exporting both does not create additional donors, spectra or
|
|
69
|
+
independent detections. Source row/sample/assay lineage stays attached to each
|
|
70
|
+
measurement.
|
|
71
|
+
|
|
54
72
|
The CLI uses the same operations:
|
|
55
73
|
|
|
56
74
|
```sh
|
|
@@ -145,4 +163,5 @@ arbitrary other taxa use the same schema. The core API needs neither pandas nor
|
|
|
145
163
|
PyArrow. Optional `orfdata[columnar]` enables `dataset.export_parquet(path)`;
|
|
146
164
|
query columns accompany full measurement JSON, preserving exact values and
|
|
147
165
|
provenance even when a floating-point analysis column cannot represent an
|
|
148
|
-
integer exactly.
|
|
166
|
+
integer exactly. [Compact storage and compatibility](storage.md) describes
|
|
167
|
+
new SQLite v4 writes, legacy readers and actual-source size/query benchmarks.
|
|
@@ -20,7 +20,7 @@ include sex. Channels and technical runs do not count as independent donors.
|
|
|
20
20
|
pip install 'orfdata[data]'
|
|
21
21
|
orfdata datasets list --species dog
|
|
22
22
|
orfdata datasets download pfizer-atlas-dog-protein
|
|
23
|
-
orfdata datasets query pfizer-atlas-dog-protein --quantity protein_abundance
|
|
23
|
+
orfdata datasets query pfizer-atlas-dog-protein --quantity protein_abundance --unit iBAQ --scale linear
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
```python
|
|
@@ -30,10 +30,13 @@ registry = DatasetRegistry()
|
|
|
30
30
|
dataset = registry.open("pfizer-atlas-dog-protein") # After explicit download.
|
|
31
31
|
reference, = dataset.manifest.references
|
|
32
32
|
group = ScopedTarget(reference, "protein_group", "A0A0A0MPB0", "MSV000099744:ProteinGroup")
|
|
33
|
-
for value in dataset.measurements(target=group):
|
|
33
|
+
for value in dataset.measurements(target=group, unit="iBAQ", scale="linear"):
|
|
34
34
|
print(value.sample_id, value.unit, value.value, value.status)
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
Omit the unit selector to return both native series. They retain the same source
|
|
38
|
+
row and sample identity; two units do not imply two independent measurements.
|
|
39
|
+
|
|
37
40
|
Every target retains its species and the checksum of the deposited UniProt
|
|
38
41
|
search FASTA. The original genomic assembly and UniProt release are unknown.
|
|
39
42
|
They stay unknown; a future annotation mapping must be explicit and validated.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Quantitative storage
|
|
2
|
+
|
|
3
|
+
ORFdata 0.8 writes compact SQLite storage version 4. `Measurement`,
|
|
4
|
+
`QuantitativeDataset`, the registry and CLI keep the same public records and
|
|
5
|
+
query semantics. Human, mouse, dog, yeast and arbitrary taxa retain separate
|
|
6
|
+
reference-scoped identities.
|
|
7
|
+
|
|
8
|
+
References, targets and assays are stored once and addressed by internal integer
|
|
9
|
+
keys. Measurements retain their values and metadata; repeated required fields
|
|
10
|
+
are reconstructed from indexed columns and the dataset manifest. Fields equal
|
|
11
|
+
to the fixed version-4 defaults are omitted from stored JSON and restored on read.
|
|
12
|
+
Zeros, nulls, censoring, uncertainty, nested provenance and derived inputs retain
|
|
13
|
+
their meaning. Foreign keys prevent cross-reference target/assay assignments.
|
|
14
|
+
Internal integer keys never appear as public biological identifiers.
|
|
15
|
+
|
|
16
|
+
Published storage versions 1, 2 and 3 remain readable. There is no automatic
|
|
17
|
+
migration or replacement of downloaded artifacts. A new source rebuild can use
|
|
18
|
+
a different storage layout and therefore a different file checksum while
|
|
19
|
+
returning the same records. Registry downloads always verify the exact published
|
|
20
|
+
artifact's hash; source builds retain their own DataCache receipts.
|
|
21
|
+
|
|
22
|
+
The implementation streams observations and bounds its target cache to 131,072
|
|
23
|
+
entries. SQLite index statistics are populated during creation so a narrow
|
|
24
|
+
target query can use the target index instead of scanning an entire reference.
|
|
25
|
+
Raw SQL table layouts are internal; use the Python/CLI query and export APIs.
|
|
26
|
+
|
|
27
|
+
## Actual-source benchmark
|
|
28
|
+
|
|
29
|
+
The first 100,000 rows of the native Pfizer/GEO RNA table contain four dog
|
|
30
|
+
libraries and 30,941 genes, including measured zeros. Source gzip SHA-256:
|
|
31
|
+
`a22780f92ffd0a028e30fe3392d73d01f787eb5a62b2043e8d26940191cbac70`.
|
|
32
|
+
This is a storage benchmark slice, not a newly released experimental dataset.
|
|
33
|
+
|
|
34
|
+
| Measurement | Storage v3 | Storage v4 |
|
|
35
|
+
|---|---:|---:|
|
|
36
|
+
| Installed size | 121.6 MB | 38.6 MB |
|
|
37
|
+
| Gzip size | 13.4 MB | 7.5 MB |
|
|
38
|
+
| Build time | 8.90 s | 4.28 s |
|
|
39
|
+
| One-gene query (four results) | 135 ms | 1.5 ms |
|
|
40
|
+
| Peak build RSS | 39.8 MB | 52.1 MB |
|
|
41
|
+
|
|
42
|
+
All 100,000 reconstructed observations were compared field for field. Timings
|
|
43
|
+
are local measurements on Python 3.12, not cross-machine guarantees; the bounded
|
|
44
|
+
target cache trades some memory for repeated-target lookup speed. Legacy tests
|
|
45
|
+
also use actual databases written by ORFdata 0.3.0, 0.4.0 and 0.5.0.
|
|
@@ -5,6 +5,8 @@ import json
|
|
|
5
5
|
import os
|
|
6
6
|
import sqlite3
|
|
7
7
|
from contextlib import closing
|
|
8
|
+
from dataclasses import MISSING, fields
|
|
9
|
+
from functools import lru_cache
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
|
|
10
12
|
from .measurements import (
|
|
@@ -16,6 +18,9 @@ from .measurements import (
|
|
|
16
18
|
MeasurementReference,
|
|
17
19
|
Sample,
|
|
18
20
|
TargetMapping,
|
|
21
|
+
_identifier,
|
|
22
|
+
_number,
|
|
23
|
+
_plain,
|
|
19
24
|
)
|
|
20
25
|
from .pairing import (
|
|
21
26
|
PAIRING_BASES,
|
|
@@ -30,38 +35,50 @@ from .reference_mapping import MappedMeasurement, ReferenceMapping
|
|
|
30
35
|
|
|
31
36
|
_SCHEMA = """
|
|
32
37
|
CREATE TABLE metadata (name TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
33
|
-
CREATE TABLE reference (
|
|
38
|
+
CREATE TABLE reference (i INTEGER PRIMARY KEY, key TEXT UNIQUE NOT NULL,
|
|
39
|
+
record TEXT NOT NULL, UNIQUE(key, i));
|
|
34
40
|
CREATE TABLE target (
|
|
35
|
-
|
|
41
|
+
i INTEGER PRIMARY KEY, reference_i INTEGER NOT NULL,
|
|
42
|
+
reference_key TEXT NOT NULL,
|
|
36
43
|
key TEXT NOT NULL, kind TEXT NOT NULL, namespace TEXT NOT NULL,
|
|
37
|
-
identifier TEXT NOT NULL,
|
|
44
|
+
identifier TEXT NOT NULL, UNIQUE(reference_key, key), UNIQUE(reference_i, i),
|
|
45
|
+
FOREIGN KEY(reference_key, reference_i) REFERENCES reference(key, i));
|
|
38
46
|
CREATE TABLE sample (
|
|
39
47
|
reference_key TEXT NOT NULL REFERENCES reference(key),
|
|
40
48
|
id TEXT NOT NULL, record TEXT NOT NULL, PRIMARY KEY(reference_key, id));
|
|
41
49
|
CREATE TABLE assay (
|
|
50
|
+
i INTEGER PRIMARY KEY, reference_i INTEGER NOT NULL,
|
|
42
51
|
reference_key TEXT NOT NULL, id TEXT NOT NULL, sample_id TEXT NOT NULL,
|
|
43
|
-
record TEXT NOT NULL,
|
|
52
|
+
record TEXT NOT NULL, UNIQUE(reference_key, id), UNIQUE(reference_i, i),
|
|
44
53
|
UNIQUE(reference_key, id, sample_id),
|
|
54
|
+
FOREIGN KEY(reference_key, reference_i) REFERENCES reference(key, i),
|
|
45
55
|
FOREIGN KEY(reference_key, sample_id) REFERENCES sample(reference_key, id));
|
|
46
56
|
CREATE TABLE mapping (
|
|
47
57
|
reference_key TEXT NOT NULL REFERENCES reference(key),
|
|
48
58
|
id TEXT NOT NULL, record TEXT NOT NULL, PRIMARY KEY(reference_key, id));
|
|
49
|
-
CREATE TABLE
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
FOREIGN KEY(
|
|
55
|
-
|
|
56
|
-
CREATE TABLE
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
CREATE TABLE observation (
|
|
60
|
+
i INTEGER PRIMARY KEY, reference_i INTEGER NOT NULL,
|
|
61
|
+
id TEXT NOT NULL, target_i INTEGER NOT NULL, assay_i INTEGER NOT NULL,
|
|
62
|
+
quantity TEXT NOT NULL, status TEXT NOT NULL, record TEXT NOT NULL,
|
|
63
|
+
UNIQUE(reference_i, id),
|
|
64
|
+
FOREIGN KEY(reference_i, target_i) REFERENCES target(reference_i, i),
|
|
65
|
+
FOREIGN KEY(reference_i, assay_i) REFERENCES assay(reference_i, i));
|
|
66
|
+
CREATE TABLE observation_input (
|
|
67
|
+
output_i INTEGER NOT NULL REFERENCES observation(i),
|
|
68
|
+
input_i INTEGER NOT NULL REFERENCES observation(i), PRIMARY KEY(output_i, input_i));
|
|
69
|
+
CREATE VIEW measurement AS
|
|
70
|
+
SELECT r.key reference_key, m.id, t.key target_key, a.sample_id,
|
|
71
|
+
a.id assay_id, m.quantity, m.status, m.record
|
|
72
|
+
FROM observation m JOIN reference r ON r.i=m.reference_i
|
|
73
|
+
JOIN target t ON t.i=m.target_i JOIN assay a ON a.i=m.assay_i;
|
|
74
|
+
CREATE VIEW input_measurement AS
|
|
75
|
+
SELECT r.key reference_key, o.id output_id, s.key input_reference_key, i.id input_id
|
|
76
|
+
FROM observation_input p JOIN observation o ON o.i=p.output_i
|
|
77
|
+
JOIN observation i ON i.i=p.input_i JOIN reference r ON r.i=o.reference_i
|
|
78
|
+
JOIN reference s ON s.i=i.reference_i;
|
|
62
79
|
CREATE INDEX target_lookup ON target(reference_key, namespace, kind, identifier);
|
|
63
|
-
CREATE INDEX observation_lookup ON
|
|
64
|
-
CREATE INDEX assay_lookup ON
|
|
80
|
+
CREATE INDEX observation_lookup ON observation(reference_i, target_i, assay_i);
|
|
81
|
+
CREATE INDEX assay_lookup ON observation(reference_i, assay_i, quantity, status);
|
|
65
82
|
CREATE TABLE assay_pair (
|
|
66
83
|
reference_key TEXT NOT NULL REFERENCES reference(key), id TEXT NOT NULL,
|
|
67
84
|
right_reference_key TEXT NOT NULL REFERENCES reference(key),
|
|
@@ -100,6 +117,42 @@ def _target_key(target):
|
|
|
100
117
|
return hashlib.sha256(_json(target.key).encode()).hexdigest()
|
|
101
118
|
|
|
102
119
|
|
|
120
|
+
_STORED_FIELDS = tuple(f for f in fields(Measurement) if f.name not in {
|
|
121
|
+
"measurement_id", "target", "sample_id", "assay_id", "quantity", "status",
|
|
122
|
+
"dataset_id", "dataset_version"})
|
|
123
|
+
_V4_DEFAULTS = {
|
|
124
|
+
"origin": "observed", "scale": "linear", "log_base": None,
|
|
125
|
+
"uncertainty": None, "uncertainty_type": None, "detection_limit": None,
|
|
126
|
+
"input_measurements": (), "input_references": (), "derivation": {}, "provenance": {},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _measurement_record(observation):
|
|
131
|
+
# Schema defaults are fixed here, independent of future model defaults.
|
|
132
|
+
# Do not serialize the target
|
|
133
|
+
# and then discard it: its full reference is already stored once.
|
|
134
|
+
result = {}
|
|
135
|
+
for field in _STORED_FIELDS:
|
|
136
|
+
value = getattr(observation, field.name)
|
|
137
|
+
default = _V4_DEFAULTS.get(field.name, MISSING)
|
|
138
|
+
if default is MISSING or value != default:
|
|
139
|
+
result[field.name] = _plain(value)
|
|
140
|
+
return result
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _validate_native_selectors(unit=None, scale=None, log_base=None):
|
|
144
|
+
if unit is not None:
|
|
145
|
+
_identifier(unit, "measurement unit selector")
|
|
146
|
+
if scale is not None and scale not in ("linear", "log"):
|
|
147
|
+
raise ValueError("Measurement scale selector must be linear or log")
|
|
148
|
+
if log_base is not None:
|
|
149
|
+
_number(log_base, "logarithm base selector")
|
|
150
|
+
if log_base <= 0 or log_base == 1:
|
|
151
|
+
raise ValueError("Logarithm base selector must be positive and not one")
|
|
152
|
+
if scale == "linear":
|
|
153
|
+
raise ValueError("A linear scale selector cannot have a logarithm base")
|
|
154
|
+
|
|
155
|
+
|
|
103
156
|
class QuantitativeDataset:
|
|
104
157
|
"""A read-only release. Constructing or querying it never downloads data.
|
|
105
158
|
|
|
@@ -112,7 +165,7 @@ class QuantitativeDataset:
|
|
|
112
165
|
self.path = Path(path).resolve()
|
|
113
166
|
with closing(self._connect()) as connection:
|
|
114
167
|
self._storage_version = connection.execute("PRAGMA user_version").fetchone()[0]
|
|
115
|
-
if self._storage_version not in (1, 2, 3):
|
|
168
|
+
if self._storage_version not in (1, 2, 3, 4):
|
|
116
169
|
raise ValueError("Unsupported quantitative database schema")
|
|
117
170
|
row = connection.execute("SELECT value FROM metadata WHERE name='manifest'").fetchone()
|
|
118
171
|
if row is None:
|
|
@@ -146,25 +199,46 @@ class QuantitativeDataset:
|
|
|
146
199
|
connection = sqlite3.connect(path)
|
|
147
200
|
connection.execute("PRAGMA foreign_keys=ON")
|
|
148
201
|
connection.executescript(_SCHEMA)
|
|
149
|
-
connection.execute("PRAGMA user_version=
|
|
202
|
+
connection.execute("PRAGMA user_version=4")
|
|
150
203
|
connection.execute("INSERT INTO metadata VALUES ('manifest', ?)",
|
|
151
204
|
(_json(manifest.to_dict()),))
|
|
205
|
+
reference_ids = {}
|
|
152
206
|
for reference in manifest.references:
|
|
153
|
-
connection.execute("INSERT INTO reference VALUES (?, ?)",
|
|
154
|
-
|
|
207
|
+
cursor = connection.execute("INSERT INTO reference (key, record) VALUES (?, ?)",
|
|
208
|
+
(reference.key, _json(reference.to_dict())))
|
|
209
|
+
reference_ids[reference.key] = cursor.lastrowid
|
|
210
|
+
|
|
211
|
+
@lru_cache(maxsize=131072)
|
|
212
|
+
def insert_target(target):
|
|
213
|
+
reference_key = target.reference.key
|
|
214
|
+
if reference_key not in reference_ids:
|
|
215
|
+
raise sqlite3.IntegrityError("Target reference absent from dataset")
|
|
216
|
+
reference_i = reference_ids[reference_key]
|
|
217
|
+
key = _target_key(target)
|
|
218
|
+
connection.execute("INSERT OR IGNORE INTO target "
|
|
219
|
+
"(reference_i, reference_key, key, kind, namespace, identifier) "
|
|
220
|
+
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
221
|
+
(reference_i, reference_key, key, target.kind, target.namespace, target.identifier))
|
|
222
|
+
target_i = connection.execute("SELECT i FROM target WHERE reference_key=? AND key=?",
|
|
223
|
+
(reference_key, key)).fetchone()[0]
|
|
224
|
+
return reference_i, target_i, key
|
|
155
225
|
for sample in samples:
|
|
156
226
|
if not isinstance(sample, Sample):
|
|
157
227
|
raise TypeError("samples must contain Samples")
|
|
158
228
|
connection.execute("INSERT INTO sample VALUES (?, ?, ?)",
|
|
159
229
|
(sample.reference.key, sample.sample_id, _json(sample.to_dict())))
|
|
230
|
+
assay_ids = {}
|
|
160
231
|
for assay in assays:
|
|
161
232
|
if not isinstance(assay, Assay):
|
|
162
233
|
raise TypeError("assays must contain Assays")
|
|
163
234
|
if assay.kind not in manifest.assay_kinds:
|
|
164
235
|
raise ValueError("Assay kind absent from manifest coverage")
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
236
|
+
reference_i = reference_ids.get(assay.reference.key)
|
|
237
|
+
cursor = connection.execute("INSERT INTO assay "
|
|
238
|
+
"(reference_i, reference_key, id, sample_id, record) VALUES (?, ?, ?, ?, ?)",
|
|
239
|
+
(reference_i, assay.reference.key, assay.assay_id, assay.sample_id,
|
|
240
|
+
_json(assay.to_dict())))
|
|
241
|
+
assay_ids[(reference_i, assay.assay_id, assay.sample_id)] = cursor.lastrowid
|
|
168
242
|
for pair in pairings:
|
|
169
243
|
if not isinstance(pair, AssayPair):
|
|
170
244
|
raise TypeError("pairings must contain AssayPairs")
|
|
@@ -194,9 +268,7 @@ class QuantitativeDataset:
|
|
|
194
268
|
if (relation.dataset_id, relation.dataset_version) != (manifest.dataset_id, manifest.version):
|
|
195
269
|
raise ValueError("Target relation belongs to a different dataset release")
|
|
196
270
|
for target in (relation.left, relation.right):
|
|
197
|
-
|
|
198
|
-
(target.reference.key, _target_key(target), target.kind,
|
|
199
|
-
target.namespace, target.identifier))
|
|
271
|
+
insert_target(target)
|
|
200
272
|
for index, (first, second) in enumerate(((relation.left, relation.right), (relation.right, relation.left))):
|
|
201
273
|
_check_one_to_one(relation_links[index],
|
|
202
274
|
(relation.relation, first.key, second.reference.key, second.kind, second.namespace), second.key,
|
|
@@ -218,9 +290,7 @@ class QuantitativeDataset:
|
|
|
218
290
|
if (mapping.dataset_id, mapping.dataset_version) != (manifest.dataset_id, manifest.version):
|
|
219
291
|
raise ValueError("Reference mapping belongs to a different dataset release")
|
|
220
292
|
for target in (mapping.source, *mapping.targets):
|
|
221
|
-
|
|
222
|
-
(target.reference.key, _target_key(target), target.kind,
|
|
223
|
-
target.namespace, target.identifier))
|
|
293
|
+
insert_target(target)
|
|
224
294
|
connection.execute("INSERT INTO reference_mapping VALUES (?, ?, ?, ?, ?)",
|
|
225
295
|
(mapping.source.reference.key, mapping.mapping_id, _target_key(mapping.source),
|
|
226
296
|
mapping.target_reference.key, _json(mapping.to_dict())))
|
|
@@ -238,33 +308,39 @@ class QuantitativeDataset:
|
|
|
238
308
|
raise ValueError("Measurement provenance names an unknown source asset")
|
|
239
309
|
if source is None and not (observation.input_measurements or observation.input_references):
|
|
240
310
|
raise ValueError("Measurement requires a source_asset or input measurements")
|
|
241
|
-
|
|
242
|
-
inputs = [(
|
|
243
|
-
inputs.extend((value.reference.key, value.measurement_id)
|
|
311
|
+
reference_i, target_i, _ = insert_target(observation.target)
|
|
312
|
+
inputs = [(reference_i, identifier) for identifier in observation.input_measurements]
|
|
313
|
+
inputs.extend((reference_ids.get(value.reference.key), value.measurement_id)
|
|
314
|
+
for value in observation.input_references)
|
|
315
|
+
input_ids = []
|
|
244
316
|
for input_reference, identifier in inputs:
|
|
245
|
-
|
|
246
|
-
"SELECT
|
|
317
|
+
found = connection.execute(
|
|
318
|
+
"SELECT i FROM observation WHERE reference_i=? AND id=?",
|
|
247
319
|
(input_reference, identifier),
|
|
248
|
-
).fetchone()
|
|
320
|
+
).fetchone()
|
|
321
|
+
if found is None:
|
|
249
322
|
raise ValueError("Derived inputs must precede outputs in their declared reference contexts")
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
323
|
+
input_ids.append(found[0])
|
|
324
|
+
assay_i = assay_ids.get((reference_i, observation.assay_id, observation.sample_id))
|
|
325
|
+
if assay_i is None:
|
|
326
|
+
raise sqlite3.IntegrityError("Observation assay/sample absent from its reference context")
|
|
327
|
+
cursor = connection.execute("INSERT INTO observation "
|
|
328
|
+
"(reference_i, id, target_i, assay_i, quantity, status, record) "
|
|
329
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
330
|
+
(reference_i, observation.measurement_id, target_i, assay_i,
|
|
331
|
+
observation.quantity, observation.status,
|
|
332
|
+
_json(_measurement_record(observation))))
|
|
333
|
+
connection.executemany("INSERT INTO observation_input VALUES (?, ?)",
|
|
334
|
+
((cursor.lastrowid, input_i) for input_i in input_ids))
|
|
335
|
+
insert_target.cache_clear()
|
|
336
|
+
counts = {table: connection.execute(
|
|
337
|
+
f"SELECT count(*) FROM {'observation' if table == 'measurement' else table}").fetchone()[0]
|
|
265
338
|
for table in ("sample", "assay", "mapping", "target", "measurement",
|
|
266
339
|
"assay_pair", "target_relation", "reference_mapping")}
|
|
267
340
|
connection.execute("INSERT INTO metadata VALUES ('counts', ?)", (_json(counts),))
|
|
341
|
+
# Real cardinalities prevent the planner from scanning a whole
|
|
342
|
+
# reference in ID order for a narrow target query.
|
|
343
|
+
connection.execute("ANALYZE")
|
|
268
344
|
connection.commit()
|
|
269
345
|
connection.close()
|
|
270
346
|
connection = None
|
|
@@ -344,8 +420,9 @@ class QuantitativeDataset:
|
|
|
344
420
|
raise ValueError("Choose explicit usable mapping statuses")
|
|
345
421
|
if type(require_sequence) is not bool:
|
|
346
422
|
raise TypeError("require_sequence must be a boolean")
|
|
347
|
-
if set(observation_filters) - {"sample_id", "assay_id", "quantity", "status"}:
|
|
423
|
+
if set(observation_filters) - {"sample_id", "assay_id", "quantity", "status", "unit", "scale", "log_base"}:
|
|
348
424
|
raise ValueError("Mapped observation filters cannot override source/target reference identity")
|
|
425
|
+
_validate_native_selectors(**{name: observation_filters.get(name) for name in ("unit", "scale", "log_base")})
|
|
349
426
|
for mapping in self.reference_mappings(target=target):
|
|
350
427
|
if mapping.status not in allowed or (require_sequence and not mapping.sequence_validated):
|
|
351
428
|
continue
|
|
@@ -407,16 +484,19 @@ class QuantitativeDataset:
|
|
|
407
484
|
tuple(by_target[1].get(relation.right.key, ())))
|
|
408
485
|
|
|
409
486
|
def measurements(self, *, target=None, reference=None, sample_id=None,
|
|
410
|
-
assay_id=None, quantity=None, status=None
|
|
487
|
+
assay_id=None, quantity=None, status=None, unit=None, scale=None,
|
|
488
|
+
log_base=None):
|
|
411
489
|
"""Iterate native observations; a target match is exact, never orthology.
|
|
412
490
|
|
|
413
491
|
Unqualified IDs across multiple references are rejected. To browse all
|
|
414
492
|
species together, omit ID filters; each result still carries its scope.
|
|
493
|
+
Native unit/scale/base selectors are exact; no conversion or aggregation.
|
|
415
494
|
"""
|
|
416
495
|
if quantity is not None and quantity not in QUANTITIES:
|
|
417
496
|
raise ValueError("Unknown measurement quantity")
|
|
418
497
|
if status is not None and status not in MEASUREMENT_STATUSES:
|
|
419
498
|
raise ValueError("Unknown measurement status")
|
|
499
|
+
_validate_native_selectors(unit, scale, log_base)
|
|
420
500
|
scope = self._scope(reference)
|
|
421
501
|
if target is not None:
|
|
422
502
|
if not isinstance(target, ScopedTarget):
|
|
@@ -430,15 +510,33 @@ class QuantitativeDataset:
|
|
|
430
510
|
filters = dict(reference_key=scope, target_key=_target_key(target) if target else None,
|
|
431
511
|
sample_id=sample_id, assay_id=assay_id, quantity=quantity, status=status)
|
|
432
512
|
selected = [(key, value) for key, value in filters.items() if value is not None]
|
|
433
|
-
query = ("SELECT m.record, m.reference_key, t.kind, t.namespace, t.identifier "
|
|
513
|
+
query = ("SELECT m.record, m.reference_key, t.kind, t.namespace, t.identifier, "
|
|
514
|
+
"m.id, m.sample_id, m.assay_id, m.quantity, m.status "
|
|
434
515
|
"FROM measurement m JOIN target t ON m.reference_key=t.reference_key "
|
|
435
516
|
"AND m.target_key=t.key")
|
|
436
|
-
|
|
437
|
-
|
|
517
|
+
conditions = [f"m.{key}=?" for key, _ in selected]
|
|
518
|
+
parameters = [value for _, value in selected]
|
|
519
|
+
# JSON extraction applies to original v1-v3 records and compact v4
|
|
520
|
+
# residual records. SQLite filters before Python reconstructs objects.
|
|
521
|
+
for value, expression in (
|
|
522
|
+
(unit, "json_extract(m.record, '$.unit')"),
|
|
523
|
+
(scale, "coalesce(json_extract(m.record, '$.scale'), 'linear')"),
|
|
524
|
+
(log_base, "json_extract(m.record, '$.log_base')"),
|
|
525
|
+
):
|
|
526
|
+
if value is not None:
|
|
527
|
+
conditions.append(expression + "=?")
|
|
528
|
+
parameters.append(value)
|
|
529
|
+
if conditions:
|
|
530
|
+
query += " WHERE " + " AND ".join(conditions)
|
|
438
531
|
query += " ORDER BY m.reference_key, m.id"
|
|
439
532
|
with closing(self._connect()) as connection:
|
|
440
|
-
for row in connection.execute(query,
|
|
533
|
+
for row in connection.execute(query, parameters):
|
|
441
534
|
values = json.loads(row[0])
|
|
535
|
+
if self._storage_version == 4:
|
|
536
|
+
values = dict(_V4_DEFAULTS, **values)
|
|
537
|
+
values.update(measurement_id=row[5], sample_id=row[6], assay_id=row[7],
|
|
538
|
+
quantity=row[8], status=row[9], dataset_id=self.manifest.dataset_id,
|
|
539
|
+
dataset_version=self.manifest.version)
|
|
442
540
|
values["target"] = ScopedTarget(self._references[row[1]], row[2], row[4], row[3])
|
|
443
541
|
if "input_references" in values:
|
|
444
542
|
values["input_references"] = tuple(MeasurementReference.from_dict(value)
|
|
@@ -39,6 +39,9 @@ def run(argv=None, prog=None):
|
|
|
39
39
|
subparser.add_argument("--assay-id")
|
|
40
40
|
subparser.add_argument("--quantity")
|
|
41
41
|
subparser.add_argument("--status")
|
|
42
|
+
subparser.add_argument("--unit", help="Exact native unit, e.g. iBAQ, rIBAQ, TPM or counts")
|
|
43
|
+
subparser.add_argument("--scale", choices=("linear", "log"))
|
|
44
|
+
subparser.add_argument("--log-base", type=float, help="Exact native logarithm base; no transformation")
|
|
42
45
|
subparser.add_argument("--target")
|
|
43
46
|
subparser.add_argument("--kind")
|
|
44
47
|
subparser.add_argument("--namespace")
|
|
@@ -111,7 +114,8 @@ def run(argv=None, prog=None):
|
|
|
111
114
|
references = {r.key: r for r in dataset.manifest.references}
|
|
112
115
|
target = ScopedTarget(references[args.reference], args.kind, args.target, args.namespace)
|
|
113
116
|
filters = dict(sample_id=args.sample, assay_id=args.assay_id,
|
|
114
|
-
quantity=args.quantity, status=args.status
|
|
117
|
+
quantity=args.quantity, status=args.status,
|
|
118
|
+
unit=args.unit, scale=args.scale, log_base=args.log_base)
|
|
115
119
|
if args.mapped:
|
|
116
120
|
if target is None:
|
|
117
121
|
parser.error("--mapped requires an exact target and reference")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.9.0"
|
|
@@ -13,6 +13,7 @@ docs/pfizer-protein.md
|
|
|
13
13
|
docs/reference.md
|
|
14
14
|
docs/source-references.md
|
|
15
15
|
docs/species.md
|
|
16
|
+
docs/storage.md
|
|
16
17
|
orfdata/__init__.py
|
|
17
18
|
orfdata/build.py
|
|
18
19
|
orfdata/catalog.py
|
|
@@ -56,6 +57,8 @@ tests/test_pfizer.py
|
|
|
56
57
|
tests/test_reference_context.py
|
|
57
58
|
tests/test_reference_mapping.py
|
|
58
59
|
tests/test_reference_orf.py
|
|
60
|
+
tests/test_selectors.py
|
|
61
|
+
tests/test_storage.py
|
|
59
62
|
tests/test_uorf_catalog.py
|
|
60
63
|
tests/data/copy/GSM1905474.soft
|
|
61
64
|
tests/data/copy/GSM1905474_KL9_fpkm.txt.gz
|
|
@@ -92,4 +95,12 @@ tests/data/references/canis_lupus_familiaris.json
|
|
|
92
95
|
tests/data/references/context-keys-v0.4.json
|
|
93
96
|
tests/data/references/homo_sapiens.json
|
|
94
97
|
tests/data/references/mus_musculus.json
|
|
95
|
-
tests/data/references/saccharomyces_cerevisiae.json
|
|
98
|
+
tests/data/references/saccharomyces_cerevisiae.json
|
|
99
|
+
tests/data/storage/README.md
|
|
100
|
+
tests/data/storage/generate.py
|
|
101
|
+
tests/data/storage/v1.json
|
|
102
|
+
tests/data/storage/v1.sqlite
|
|
103
|
+
tests/data/storage/v2.json
|
|
104
|
+
tests/data/storage/v2.sqlite
|
|
105
|
+
tests/data/storage/v3.json
|
|
106
|
+
tests/data/storage/v3.sqlite
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
These synthetic databases were generated by the **released** ORFdata versions
|
|
2
|
+
0.3.0 (storage 1), 0.4.0 (storage 2) and 0.5.0 (storage 3), using `generate.py`.
|
|
3
|
+
They test actual legacy layouts; changing a new database's `user_version` would
|
|
4
|
+
not produce a valid old-format fixture.
|
|
5
|
+
|
|
6
|
+
Each contains four species with colliding local sample/assay/target/measurement
|
|
7
|
+
IDs, measured zero, a censored value, uncertainty and derived input lineage.
|
|
8
|
+
Versions 2/3 include pairings; version 3 includes reference mappings. No records
|
|
9
|
+
are experimental evidence. The JSON files retain the original writer's records.
|
|
10
|
+
|
|
11
|
+
Example regeneration from a checkout of the appropriate release:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
PYTHONPATH=/path/to/orfdata-0.3.0 python generate.py /path/to/fixtures 1
|
|
15
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Run from released 0.3.0/0.4.0/0.5.0 to generate genuine legacy databases."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import orfdata
|
|
9
|
+
from orfdata import (
|
|
10
|
+
Assay,
|
|
11
|
+
DatasetManifest,
|
|
12
|
+
Measurement,
|
|
13
|
+
QuantitativeDataset,
|
|
14
|
+
ReferenceContext,
|
|
15
|
+
Sample,
|
|
16
|
+
ScopedTarget,
|
|
17
|
+
SourceAsset,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
output = Path(sys.argv[1])
|
|
21
|
+
version = int(sys.argv[2])
|
|
22
|
+
references = tuple(ReferenceContext(taxon, name, assembly, "ensembl", "110")
|
|
23
|
+
for taxon, name, assembly in ((9606, "human", "GRCh38"),
|
|
24
|
+
(10090, "mouse", "GRCm39"),
|
|
25
|
+
(9615, "dog", "ROS_Cfam_1.0"),
|
|
26
|
+
(4932, "yeast", "R64-1-1")))
|
|
27
|
+
source = SourceAsset("input.tsv", "https://example.invalid/storage-fixture.tsv",
|
|
28
|
+
hashlib.sha256(b"synthetic fixture\n").hexdigest(), 18,
|
|
29
|
+
"Synthetic test data; Apache-2.0", redistributable=True)
|
|
30
|
+
manifest = DatasetManifest("legacy-fixture", "1", "Synthetic legacy storage validation",
|
|
31
|
+
references, (source,), ("Synthetic, not experimental evidence",),
|
|
32
|
+
("rna_seq", "digest_ms"), {"scope": "four deliberately colliding species"},
|
|
33
|
+
{"writer": "orfdata", "writer_version": orfdata.__version__})
|
|
34
|
+
samples, assays, observations, pairs, relations, mappings = [], [], [], [], [], []
|
|
35
|
+
for index, reference in enumerate(references):
|
|
36
|
+
samples.append(Sample("sample", reference, biological_unit_id="culture"))
|
|
37
|
+
assays.extend((Assay("rna", "sample", "rna_seq", reference), Assay("protein", "sample", "digest_ms", reference)))
|
|
38
|
+
gene = ScopedTarget(reference, "gene", "same-id", "fixture")
|
|
39
|
+
protein = ScopedTarget(reference, "protein", "same-id", "fixture")
|
|
40
|
+
observations.extend((
|
|
41
|
+
Measurement("rna-value", gene, "sample", "rna", "rna_abundance", index, "TPM",
|
|
42
|
+
manifest.dataset_id, manifest.version, provenance={"source_asset": "input.tsv", "row": index}),
|
|
43
|
+
Measurement("protein-value", protein, "sample", "protein", "protein_abundance",
|
|
44
|
+
None if index == 1 else 10.25 + index, "iBAQ", manifest.dataset_id, manifest.version,
|
|
45
|
+
status="below_detection" if index == 1 else "measured", detection_limit=20 if index == 1 else None,
|
|
46
|
+
uncertainty=0.5, uncertainty_type="standard_deviation",
|
|
47
|
+
provenance={"source_asset": "input.tsv", "row": index, "native": "<20" if index == 1 else "observed"}),
|
|
48
|
+
Measurement("derived", gene, "sample", "rna", "rna_abundance", 1, "fixture-units",
|
|
49
|
+
manifest.dataset_id, manifest.version, origin="derived", input_measurements=("rna-value",),
|
|
50
|
+
derivation={"method": "synthetic lineage fixture"}),
|
|
51
|
+
))
|
|
52
|
+
if version >= 2:
|
|
53
|
+
from orfdata import AssayPair, TargetRelation
|
|
54
|
+
|
|
55
|
+
pairs.append(AssayPair("pair", reference, manifest.dataset_id, manifest.version,
|
|
56
|
+
"rna", "protein", "same_material", {"source_asset": "input.tsv"}, "concurrent"))
|
|
57
|
+
relations.append(TargetRelation("relation", manifest.dataset_id, manifest.version, gene, protein,
|
|
58
|
+
"canonical_translation", "one_to_one", {"source_asset": "input.tsv"}))
|
|
59
|
+
if version >= 3:
|
|
60
|
+
from orfdata import ReferenceMapping
|
|
61
|
+
|
|
62
|
+
mappings.append(ReferenceMapping("map", manifest.dataset_id, manifest.version,
|
|
63
|
+
protein, reference, (ScopedTarget(reference, "protein", "mapped-id", "fixture"),),
|
|
64
|
+
"unique", "synthetic identifier match", {"sequence": "not validated"},
|
|
65
|
+
provenance={"source_asset": "input.tsv"}))
|
|
66
|
+
kwargs = dict(samples=samples, assays=assays, measurements=observations)
|
|
67
|
+
if version >= 2:
|
|
68
|
+
kwargs.update(pairings=pairs, relations=relations)
|
|
69
|
+
if version >= 3:
|
|
70
|
+
kwargs.update(reference_mappings=mappings)
|
|
71
|
+
dataset = QuantitativeDataset.create(output / f"v{version}.sqlite", manifest, **kwargs)
|
|
72
|
+
expected = dict(writer_version=orfdata.__version__, manifest=manifest.to_dict(),
|
|
73
|
+
measurements=[m.to_dict() for m in observations], counts=dataset.counts)
|
|
74
|
+
(output / f"v{version}.json").write_text(json.dumps(expected, indent=2) + "\n")
|