scigantic-emdb 0.1.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.
- scigantic_emdb-0.1.0/LICENSE +21 -0
- scigantic_emdb-0.1.0/PKG-INFO +116 -0
- scigantic_emdb-0.1.0/README.md +88 -0
- scigantic_emdb-0.1.0/pyproject.toml +38 -0
- scigantic_emdb-0.1.0/scigantic_emdb/__init__.py +640 -0
- scigantic_emdb-0.1.0/scigantic_emdb.egg-info/PKG-INFO +116 -0
- scigantic_emdb-0.1.0/scigantic_emdb.egg-info/SOURCES.txt +10 -0
- scigantic_emdb-0.1.0/scigantic_emdb.egg-info/dependency_links.txt +1 -0
- scigantic_emdb-0.1.0/scigantic_emdb.egg-info/requires.txt +15 -0
- scigantic_emdb-0.1.0/scigantic_emdb.egg-info/top_level.txt +1 -0
- scigantic_emdb-0.1.0/setup.cfg +4 -0
- scigantic_emdb-0.1.0/tests/test_search.py +534 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scigantic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scigantic-emdb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Search every structure in EMDB from Python — 60,895 cryo-EM maps by protein, organism, resolution, molecular weight and PDB cross-reference, then read one in two calls.
|
|
5
|
+
Author: Scigantic
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/scigantic/scigantic-emdb
|
|
8
|
+
Project-URL: EMDB, https://www.ebi.ac.uk/emdb/
|
|
9
|
+
Project-URL: EMPIAR (raw data), https://github.com/scigantic/scigantic-empiar
|
|
10
|
+
Keywords: cryo-em,cryo-et,emdb,empiar,mrc,structural-biology,microscopy
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Requires-Dist: pandas
|
|
16
|
+
Requires-Dist: scigantic-empiar>=0.3.0
|
|
17
|
+
Provides-Extra: maps
|
|
18
|
+
Requires-Dist: numpy; extra == "maps"
|
|
19
|
+
Requires-Dist: mrcfile; extra == "maps"
|
|
20
|
+
Requires-Dist: matplotlib; extra == "maps"
|
|
21
|
+
Requires-Dist: ipython; extra == "maps"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Requires-Dist: numpy; extra == "dev"
|
|
25
|
+
Requires-Dist: mrcfile; extra == "dev"
|
|
26
|
+
Requires-Dist: matplotlib; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# scigantic-emdb
|
|
30
|
+
|
|
31
|
+
Search every structure in [EMDB](https://www.ebi.ac.uk/emdb/) — EMBL-EBI's public archive of **3D cryo-EM density maps** — from Python, and read one in two calls.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install "scigantic-emdb[maps]"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from scigantic_emdb import EmdbCatalog, load_map, slices
|
|
39
|
+
|
|
40
|
+
cat = EmdbCatalog()
|
|
41
|
+
cat.search("GPCR", organism="Homo sapiens", max_res=3.0, sort="resolution")
|
|
42
|
+
|
|
43
|
+
vol, meta = load_map("EMD-22962") # decompresses, caches, opens
|
|
44
|
+
slices(vol) # central XY / XZ / YZ sections
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Why
|
|
48
|
+
|
|
49
|
+
EMDB's I/O is easy: a map is 20–60 MB and `mrcfile` opens it. What EMDB lacks is any way to answer *"which of these 60,895 structures do I want"* short of already knowing the accession. This is that index.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
cat.search("spliceosome", max_res=3.5)
|
|
53
|
+
cat.search("GPCR", max_chain_kda=100, has_half_maps=True)
|
|
54
|
+
cat.search("protease", ligand="ATP", has_model=True)
|
|
55
|
+
cat.search("capsid", microscope="KRIOS", min_year=2023)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`max_chain_kda` is the largest **single protein chain**, which is the filter you want for "a receptor under 100 kDa". The assembled complex carries the G protein and any nanobodies, so it is almost always heavier than the molecule of interest — measured across the catalog, entries with a chain ≤100 kDa have a *median complex weight of 240 kDa*.
|
|
59
|
+
|
|
60
|
+
## Coverage is measured, not asserted
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
cat.coverage()
|
|
64
|
+
# {'catalog_entries': 60895, 'ftp_released_entries': 60895, 'coverage_pct': 100.0, ...}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Every released EMDB entry is in the index. Getting there took two sources: EBI Search indexes only 46,900 of them (76.9%), so the remaining 14,042 come from the per-entry REST API.
|
|
68
|
+
|
|
69
|
+
Molecular fields come from what each group chose to deposit and are **not universal**:
|
|
70
|
+
|
|
71
|
+
| field | fill | | field | fill |
|
|
72
|
+
|---|---|---|---|---|
|
|
73
|
+
| `microscope`, `box`, `map_mb` | 100% | | `complex_kda` | 59.1% |
|
|
74
|
+
| `image` | 99.0% | | `max_chain_kda` | 58.5% |
|
|
75
|
+
| `contour_level` | 95.2% | | `ligands` | 37.2% |
|
|
76
|
+
| `has_half_maps` | 67.2% | | `has_mask` | 26.4% |
|
|
77
|
+
|
|
78
|
+
A record missing the field being filtered on is **excluded**, never silently kept. So "12 structures match" means twelve among those that deposited a weight, not twelve in EMDB. Read the real numbers from `cat.coverage()["enrichment"]["field_fill_pct"]` rather than presenting a filtered count as the whole picture.
|
|
79
|
+
|
|
80
|
+
## Galleries cost nothing
|
|
81
|
+
|
|
82
|
+
EBI already publishes a rendered isosurface for ~97% of entries, so a gallery involves no map reads and copies no pixels — the catalog stores the filename and the card points at EBI's public URL.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
cat.gallery(cat.search("spliceosome", max_res=4.0).head(8))
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Cross-archive: which of these can I reprocess?
|
|
89
|
+
|
|
90
|
+
EMDB says what was solved; [EMPIAR](https://github.com/scigantic/scigantic-empiar) says whether the raw movies are public. Neither answers this alone.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
cat.with_empiar(cat.search("GPCR", max_res=3.0))
|
|
94
|
+
# emdb_id resolution_a empiar_id raw_size_gb
|
|
95
|
+
# EMD-13880 2.70 10877 4198.4
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Search behaviour worth knowing
|
|
99
|
+
|
|
100
|
+
Free text runs over title, sample name, organism, method and accessions, with a small cryo-EM synonym vocabulary, and what you literally typed always ranks first.
|
|
101
|
+
|
|
102
|
+
Expansion is a **recall fallback, not a query rewrite**. Spelling variants (`cryoet` → `cryo-et`, `ribosome` → `ribosomal`) always apply. Family expansion (`GPCR` → its 30+ member receptors) engages only when the literal query is thin, because at 60,895 entries an unconditional rewrite turned `search("rhodopsin")` into 1,189 hits of which only 40 mentioned rhodopsin. `cat.last_query_expanded` tells you which happened.
|
|
103
|
+
|
|
104
|
+
Ligand abbreviations resolve to deposited chemical names: `ligand="ATP"` matches the 965 entries deposited as `ADENOSINE-5'-TRIPHOSPHATE`, not the 4 that spell it "ATP".
|
|
105
|
+
|
|
106
|
+
## Relationship to scigantic-empiar
|
|
107
|
+
|
|
108
|
+
The query layer is **imported** from [`scigantic-empiar`](https://pypi.org/project/scigantic-empiar/), never copied. Both archives share one implementation, so the fixes it carries cannot diverge between them. That is also why `with_empiar()` works without extra setup.
|
|
109
|
+
|
|
110
|
+
## Notes
|
|
111
|
+
|
|
112
|
+
- The catalog is a prebuilt index fetched over HTTPS (~13 MB gzipped, loads in about two seconds). Nothing is downloaded until you read a map.
|
|
113
|
+
- `load_map()` works with or without the archive mounted; off-mount it fetches from EBI.
|
|
114
|
+
- `entry_files()` reports only what an entry actually deposited. Half-maps, masks and FSC curves are per-deposition, so check rather than assume.
|
|
115
|
+
|
|
116
|
+
MIT licensed. EMDB data is CC0; please cite EMDB.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# scigantic-emdb
|
|
2
|
+
|
|
3
|
+
Search every structure in [EMDB](https://www.ebi.ac.uk/emdb/) — EMBL-EBI's public archive of **3D cryo-EM density maps** — from Python, and read one in two calls.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install "scigantic-emdb[maps]"
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from scigantic_emdb import EmdbCatalog, load_map, slices
|
|
11
|
+
|
|
12
|
+
cat = EmdbCatalog()
|
|
13
|
+
cat.search("GPCR", organism="Homo sapiens", max_res=3.0, sort="resolution")
|
|
14
|
+
|
|
15
|
+
vol, meta = load_map("EMD-22962") # decompresses, caches, opens
|
|
16
|
+
slices(vol) # central XY / XZ / YZ sections
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Why
|
|
20
|
+
|
|
21
|
+
EMDB's I/O is easy: a map is 20–60 MB and `mrcfile` opens it. What EMDB lacks is any way to answer *"which of these 60,895 structures do I want"* short of already knowing the accession. This is that index.
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
cat.search("spliceosome", max_res=3.5)
|
|
25
|
+
cat.search("GPCR", max_chain_kda=100, has_half_maps=True)
|
|
26
|
+
cat.search("protease", ligand="ATP", has_model=True)
|
|
27
|
+
cat.search("capsid", microscope="KRIOS", min_year=2023)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`max_chain_kda` is the largest **single protein chain**, which is the filter you want for "a receptor under 100 kDa". The assembled complex carries the G protein and any nanobodies, so it is almost always heavier than the molecule of interest — measured across the catalog, entries with a chain ≤100 kDa have a *median complex weight of 240 kDa*.
|
|
31
|
+
|
|
32
|
+
## Coverage is measured, not asserted
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
cat.coverage()
|
|
36
|
+
# {'catalog_entries': 60895, 'ftp_released_entries': 60895, 'coverage_pct': 100.0, ...}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Every released EMDB entry is in the index. Getting there took two sources: EBI Search indexes only 46,900 of them (76.9%), so the remaining 14,042 come from the per-entry REST API.
|
|
40
|
+
|
|
41
|
+
Molecular fields come from what each group chose to deposit and are **not universal**:
|
|
42
|
+
|
|
43
|
+
| field | fill | | field | fill |
|
|
44
|
+
|---|---|---|---|---|
|
|
45
|
+
| `microscope`, `box`, `map_mb` | 100% | | `complex_kda` | 59.1% |
|
|
46
|
+
| `image` | 99.0% | | `max_chain_kda` | 58.5% |
|
|
47
|
+
| `contour_level` | 95.2% | | `ligands` | 37.2% |
|
|
48
|
+
| `has_half_maps` | 67.2% | | `has_mask` | 26.4% |
|
|
49
|
+
|
|
50
|
+
A record missing the field being filtered on is **excluded**, never silently kept. So "12 structures match" means twelve among those that deposited a weight, not twelve in EMDB. Read the real numbers from `cat.coverage()["enrichment"]["field_fill_pct"]` rather than presenting a filtered count as the whole picture.
|
|
51
|
+
|
|
52
|
+
## Galleries cost nothing
|
|
53
|
+
|
|
54
|
+
EBI already publishes a rendered isosurface for ~97% of entries, so a gallery involves no map reads and copies no pixels — the catalog stores the filename and the card points at EBI's public URL.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
cat.gallery(cat.search("spliceosome", max_res=4.0).head(8))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Cross-archive: which of these can I reprocess?
|
|
61
|
+
|
|
62
|
+
EMDB says what was solved; [EMPIAR](https://github.com/scigantic/scigantic-empiar) says whether the raw movies are public. Neither answers this alone.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
cat.with_empiar(cat.search("GPCR", max_res=3.0))
|
|
66
|
+
# emdb_id resolution_a empiar_id raw_size_gb
|
|
67
|
+
# EMD-13880 2.70 10877 4198.4
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Search behaviour worth knowing
|
|
71
|
+
|
|
72
|
+
Free text runs over title, sample name, organism, method and accessions, with a small cryo-EM synonym vocabulary, and what you literally typed always ranks first.
|
|
73
|
+
|
|
74
|
+
Expansion is a **recall fallback, not a query rewrite**. Spelling variants (`cryoet` → `cryo-et`, `ribosome` → `ribosomal`) always apply. Family expansion (`GPCR` → its 30+ member receptors) engages only when the literal query is thin, because at 60,895 entries an unconditional rewrite turned `search("rhodopsin")` into 1,189 hits of which only 40 mentioned rhodopsin. `cat.last_query_expanded` tells you which happened.
|
|
75
|
+
|
|
76
|
+
Ligand abbreviations resolve to deposited chemical names: `ligand="ATP"` matches the 965 entries deposited as `ADENOSINE-5'-TRIPHOSPHATE`, not the 4 that spell it "ATP".
|
|
77
|
+
|
|
78
|
+
## Relationship to scigantic-empiar
|
|
79
|
+
|
|
80
|
+
The query layer is **imported** from [`scigantic-empiar`](https://pypi.org/project/scigantic-empiar/), never copied. Both archives share one implementation, so the fixes it carries cannot diverge between them. That is also why `with_empiar()` works without extra setup.
|
|
81
|
+
|
|
82
|
+
## Notes
|
|
83
|
+
|
|
84
|
+
- The catalog is a prebuilt index fetched over HTTPS (~13 MB gzipped, loads in about two seconds). Nothing is downloaded until you read a map.
|
|
85
|
+
- `load_map()` works with or without the archive mounted; off-mount it fetches from EBI.
|
|
86
|
+
- `entry_files()` reports only what an entry actually deposited. Half-maps, masks and FSC curves are per-deposition, so check rather than assume.
|
|
87
|
+
|
|
88
|
+
MIT licensed. EMDB data is CC0; please cite EMDB.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "scigantic-emdb"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Search every structure in EMDB from Python — 60,895 cryo-EM maps by protein, organism, resolution, molecular weight and PDB cross-reference, then read one in two calls."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Scigantic" }]
|
|
13
|
+
keywords = ["cryo-em", "cryo-et", "emdb", "empiar", "mrc", "structural-biology", "microscopy"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"requests",
|
|
16
|
+
"pandas",
|
|
17
|
+
# The query layer (synonym vocabulary, whole-word plural-tolerant matching,
|
|
18
|
+
# literal-over-synonym ranking) lives in scigantic-empiar and is imported,
|
|
19
|
+
# never copied — one implementation, so its fixes cannot diverge. 0.3.0 is
|
|
20
|
+
# the first release at parity with the shipping source.
|
|
21
|
+
"scigantic-empiar>=0.3.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
# load_map() reads CCP4/MRC volumes; slices()/gallery() render them.
|
|
26
|
+
maps = ["numpy", "mrcfile", "matplotlib", "ipython"]
|
|
27
|
+
dev = ["pytest", "numpy", "mrcfile", "matplotlib"]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/scigantic/scigantic-emdb"
|
|
31
|
+
"EMDB" = "https://www.ebi.ac.uk/emdb/"
|
|
32
|
+
"EMPIAR (raw data)" = "https://github.com/scigantic/scigantic-empiar"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools]
|
|
35
|
+
packages = ["scigantic_emdb"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|