Find and load datasets#
This guide walks the whole read path: filter the catalog, search it, find datasets by gene, inspect a
store without downloading it, and load it as SpatialData. It runs against the live catalog; the
last cell prints which catalog snapshot the outputs came from.
pip install git+https://github.com/theislab/spatialdata-db-client
import sddb
cat = sddb.Catalog()
cat
<Catalog: 1778 datasets>
Catalog() fetches the published catalog.parquet once and caches it. query() and search() return a
SpatialDataCohort: an ordered, sliceable list of Dataset records that never touches the zarr stores.
Facets#
The distinct values of a facet column are plain pandas on the catalog table.
df = cat.to_df()
sorted(df["organism"].dropna().unique())
['arabidopsis thaliana', 'glycine max', 'human', 'mouse', 'rat', 'zebrafish']
Any other facet column works the same way (sddb-cli facets lists them from the command line).
A facet keyword takes a scalar (equality) or a list (any of). Rows that failed validation are dropped
by default (validation="pass").
scalar = cat.query(organism="human", technology="Visium", tissue="lung")
scalar
<SpatialDataCohort: 37 datasets>
several = cat.query(organism="human", technology=["Visium", "VisiumHD"], tissue="lung")
several.summary()
{'n_objects': 44, 'n_studies': 43, 'technologies': ['Visium', 'VisiumHD']}
several.groupby("technology")
| technology | n | |
|---|---|---|
| 0 | Visium | 37 |
| 1 | VisiumHD | 7 |
Ranges#
Append __gte, __gt, __lte or __lt to a numeric column. Missing values never match.
big = cat.query(organism="human", technology=["Visium", "VisiumHD"], n_obs__gte=50_000)
big.to_df()[["uid", "technology", "tissue", "n_obs"]].head()
| uid | technology | tissue | n_obs | |
|---|---|---|---|---|
| 0 | haOzFbJ7Bc9x7NW60001 | VisiumHD | ovary | 475742 |
| 1 | 2TR7434o4LgFuwCi0000 | VisiumHD | brain | 521175 |
| 2 | TfXXJl6p6e5ZtUxI0000 | VisiumHD | tonsil | 553820 |
| 3 | wkdYCDgiAN7RJEZb0000 | VisiumHD | lung | 605471 |
| 4 | k5Q3YIsLRHtzFqFR0000 | VisiumHD | lung | 448109 |
Free-text search#
search() fuzzy-matches your words against the distinct values of each facet. Matches inside one facet are
OR-ed, facets are AND-ed. The facet values that matched are kept on .matched.
hits = cat.search("human lung visium")
print(hits)
hits.matched["organism"], hits.matched["tissue"], hits.matched["technology"]
<SpatialDataCohort: 5 datasets>
(['human'], ['lung'], ['Visium', 'VisiumHD'])
.matched maps each facet to the values your words matched. Free text is matched against every facet, including
study_id (156 study ids contain “human”, “lung” or “visium”), so the facets are AND-ed to narrow the result.
query(search=...) combines the same search with facets and ranges.
cat.query(search="femur", organism="human").to_df()[["uid", "technology", "tissue", "disease"]]
| uid | technology | tissue | disease | |
|---|---|---|---|---|
| 0 | 6Qsv3u7rqNbjyIAr0000 | Visium | head of femur | osteoarthritis |
| 1 | qzSwmOf7s7QlKQBR0000 | Visium | head of femur | osteoarthritis |
| 2 | cg5E9tnrKkdg7jDq0000 | Visium | head of femur | osteoarthritis |
| 3 | cgmLrEw3hgv7yhZf0000 | Visium | head of femur | unknown |
Find datasets by gene#
expressing= keeps datasets where a gene (symbol or Ensembl id) is detected. min_fraction sets the
minimum fraction of observations in which it is detected; a list of genes means all of them
(mode="any" for the union).
Note
The first gene query downloads the cross-dataset gene index (~260 MB) into the cache. Later queries, and later sessions, reuse it.
epcam = cat.query(expressing=["EPCAM"], min_fraction=0.5, technology="Visium", organism="human", tissue="lung")
print(epcam)
cat.genes.ranked("EPCAM").head(3)
<SpatialDataCohort: 4 datasets>
| uid | fraction_obs_detected | total_counts | |
|---|---|---|---|
| 0 | EVTMdgu8thDMETSm0000 | 1.0 | 38068.0 |
| 1 | bOgBgMqQm9TObx470000 | 0.999516 | 361511.0 |
| 2 | TeLy8jHRRvoCrNMn0000 | 0.99895 | 276076.0 |
Pick a small dataset#
The datasets below are among the smallest stores in the catalog (about 10 MB each), so this guide stays fast. Each links to its page on the DB site.
uid |
technology |
tissue |
site page |
|---|---|---|---|
|
VisiumHD |
bone element |
|
|
Visium |
head of femur |
small = cat.cohort(uids=["xVK6T0rajFTgOKci0000", "cgmLrEw3hgv7yhZf0000"])
small.to_df()[["uid", "technology", "tissue", "organism", "n_obs", "size_bytes"]]
| uid | technology | tissue | organism | n_obs | size_bytes | |
|---|---|---|---|---|---|---|
| 0 | cgmLrEw3hgv7yhZf0000 | Visium | head of femur | human | 4992 | 9919670 |
| 1 | xVK6T0rajFTgOKci0000 | VisiumHD | bone element | human | 13627 | 9475378 |
Inspect without downloading#
Dataset.elements() reads only the zarr metadata and returns the shape and dtype of every element.
hd = small.filter(technology="VisiumHD")[0]
print(hd)
for name, info in hd.elements().items():
print(f"{name:55s} {info['shape']} {info['dtype']}")
<Dataset xVK6T0rajFTgOKci0000 technology=VisiumHD organism=human>
images/GSM9556004_033_post_hires_image (3, 1906, 6000) uint8
images/GSM9556004_033_post_lowres_image (3, 191, 600) uint8
shapes/GSM9556004_033_post_square_008um None None
shapes/GSM9556004_033_post_square_008um_for_vitessce None None
tables/square_008um (13627, 18085) float32
Load#
A remote store cannot be opened lazily yet, so ds.load() with its default lazy=True raises
NotImplementedError. Pass lazy=False to copy the store into the cache and open it.
try:
hd.load()
except NotImplementedError as err:
print("NotImplementedError:", str(err).splitlines()[0])
NotImplementedError: Remote lazy open isn't supported upstream (spatialdata). Download first: `SpatialDataCohort.download(dest)` then open the local store, or use `load(lazy=False)` to materialise.
sdata = hd.load(lazy=False)
print("images:", list(sdata.images))
print("tables:", {name: t.shape for name, t in sdata.tables.items()})
images: ['GSM9556004_033_post_hires_image', 'GSM9556004_033_post_lowres_image']
tables: {'square_008um': (13627, 18085)}
Load only some elements#
elements=["kind/name"] copies only what you ask for, which matters when a store holds large images.
sdata_tbl = hd.load(lazy=False, elements=["tables/square_008um"])
print("images:", list(sdata_tbl.images))
print("tables:", {name: t.shape for name, t in sdata_tbl.tables.items()})
images: []
tables: {'square_008um': (13627, 18085)}
sddb.open_sdata does the same from a bare store URL, for when you have a zarr_url but no catalog row.
from sddb import open_sdata
sdata_url = open_sdata(hd.zarr_url, lazy=False, elements=["tables/square_008um"])
list(sdata_url.tables)
['square_008um']
View in the browser#
viewer_url() is a pure string transform of the catalog’s vitessce_url, so it needs no download.
url = hd.viewer_url()
print(url[:60] + "...", f"({len(url)} characters)")
https://vitessce.io#?edit=false&url=data:,%257B%2522version%... (8932 characters)
Dataset.view() opens that link in your default browser, and Dataset.view_interactive() renders it inside a
notebook. Both need the viz extra and a desktop session, so they are shown here and not executed:
# pip install "spatialdata-db[viz] @ git+https://github.com/theislab/spatialdata-db-client"
hd.view() # opens vitessce.io in your browser
hd.view_interactive() # in-notebook widget
print("Catalog generated at:", cat.generated_at)
Catalog generated at: 2026-10-18