Demystifying the .pxl File: A Scientist’s Guide to Single-Cell Spatial Proteomics Data
Published:
If you’ve started working with Pixelgen Technologies’ Proximity Network Assay (PNA), you’ve probably noticed that every analysis starts from a single, slightly mysterious file: the .pxl file. This post walks through exactly what that file is, what’s inside it, and how to explore it yourself with nothing more than Python and DuckDB — before you ever touch Scanpy or Seurat.
What even is a .pxl file?
PNA is a method for mapping the spatial arrangement of proteins on the surface of individual cells. Sequencing a PNA experiment produces millions of reads describing which barcoded antibodies sat near each other on a cell’s surface. Pixelator (the processing pipeline) turns those raw reads into components — connected subgraphs that each represent a single cell (or occasionally a doublet, cell fragment, or antibody aggregate) — and packages the results into a .pxl file.
Under the hood, a .pxl file is a DuckDB database. That’s the key insight: you don’t need any special parser to look inside one. You can open it with duckdb.connect() and run plain SQL, exactly like you would against any other relational database.
Setting up
To follow along, install pixelator and grab a public PNA dataset:
!pip install pixelgen-pixelator
Requirement already satisfied: pixelgen-pixelator in /usr/local/lib/python3.13/dist-packages (0.30.0)
Requirement already satisfied: anndata>=0.8.0 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (0.13.4)
Requirement already satisfied: annoy<=1.17.0 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (1.17.0)
Requirement already satisfied: click<8.3 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (8.2.1)
Requirement already satisfied: cutadapt>=4.2 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (5.2)
Requirement already satisfied: duckdb<2,>=1.4.2 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (1.5.6)
Requirement already satisfied: faiss-cpu<2,>=1.10.0 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (1.15.1)
Requirement already satisfied: fsspec in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (2025.12.0)
Requirement already satisfied: graspologic-native<2,>=1.2.4 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (1.3.1)
Requirement already satisfied: importlib-resources<6,>=5.12.0 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (5.13.0)
Requirement already satisfied: levenshtein>=0.27.3 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (0.27.5)
Requirement already satisfied: numba>=0.56.4 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (0.61.2)
Requirement already satisfied: numpy<3.0.0,>=2.0.0 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (2.1.3)
Requirement already satisfied: pandas<3.0.0,>=2.0.0 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (2.3.3)
Requirement already satisfied: pixelgen-pixelator-core<0.2,>=0.1.0 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (0.1.1)
Requirement already satisfied: plotly in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (5.24.1)
Requirement already satisfied: polars<2.0.0,>=1.0.0 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (1.35.2)
Requirement already satisfied: pyarrow>=14 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (23.0.1)
Requirement already satisfied: pydantic<3.0,>=2.12.4 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (2.13.5)
Requirement already satisfied: pyfastx in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (2.3.1)
Requirement already satisfied: requests>=2.32.5 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (2.34.2)
Requirement already satisfied: ruamel-yaml<0.18,>=0.17.21 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (0.17.40)
Requirement already satisfied: scanpy in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (1.12.4)
Requirement already satisfied: scipy<2.0.0,>=1.11.0 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (1.16.3)
Requirement already satisfied: semver<4,>=3.0.0 in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (3.1.0)
Requirement already satisfied: typing-extensions in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (4.16.0)
Requirement already satisfied: xopen[zstd] in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (2.1.0)
Requirement already satisfied: xxhash in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (4.0.1)
Requirement already satisfied: yappi in /usr/local/lib/python3.13/dist-packages (from pixelgen-pixelator) (1.7.6)
Requirement already satisfied: array-api-compat>=1.7.1 in /usr/local/lib/python3.13/dist-packages (from anndata>=0.8.0->pixelgen-pixelator) (1.15.0)
Requirement already satisfied: h5py>=3.11 in /usr/local/lib/python3.13/dist-packages (from anndata>=0.8.0->pixelgen-pixelator) (3.14.0)
Requirement already satisfied: legacy-api-wrap in /usr/local/lib/python3.13/dist-packages (from anndata>=0.8.0->pixelgen-pixelator) (1.5)
Requirement already satisfied: natsort in /usr/local/lib/python3.13/dist-packages (from anndata>=0.8.0->pixelgen-pixelator) (8.4.0)
Requirement already satisfied: packaging>=24.2 in /usr/local/lib/python3.13/dist-packages (from anndata>=0.8.0->pixelgen-pixelator) (26.3)
Requirement already satisfied: scverse-misc>=0.1.0 in /usr/local/lib/python3.13/dist-packages (from scverse-misc[settings]>=0.1.0->anndata>=0.8.0->pixelgen-pixelator) (0.1.6)
Requirement already satisfied: zarr>=3.1 in /usr/local/lib/python3.13/dist-packages (from anndata>=0.8.0->pixelgen-pixelator) (3.4.1)
Requirement already satisfied: dnaio>=1.2.3 in /usr/local/lib/python3.13/dist-packages (from cutadapt>=4.2->pixelgen-pixelator) (1.2.4)
Requirement already satisfied: rapidfuzz<4.0.0,>=3.9.0 in /usr/local/lib/python3.13/dist-packages (from levenshtein>=0.27.3->pixelgen-pixelator) (3.14.6)
Requirement already satisfied: llvmlite<0.45,>=0.44.0dev0 in /usr/local/lib/python3.13/dist-packages (from numba>=0.56.4->pixelgen-pixelator) (0.44.0)
Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.13/dist-packages (from pandas<3.0.0,>=2.0.0->pixelgen-pixelator) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.13/dist-packages (from pandas<3.0.0,>=2.0.0->pixelgen-pixelator) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.13/dist-packages (from pandas<3.0.0,>=2.0.0->pixelgen-pixelator) (2026.4)
Requirement already satisfied: polars-runtime-32==1.35.2 in /usr/local/lib/python3.13/dist-packages (from polars<2.0.0,>=1.0.0->pixelgen-pixelator) (1.35.2)
Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.13/dist-packages (from pydantic<3.0,>=2.12.4->pixelgen-pixelator) (0.8.0)
Requirement already satisfied: pydantic-core==2.46.5 in /usr/local/lib/python3.13/dist-packages (from pydantic<3.0,>=2.12.4->pixelgen-pixelator) (2.46.5)
Requirement already satisfied: typing-inspection>=0.4.2 in /usr/local/lib/python3.13/dist-packages (from pydantic<3.0,>=2.12.4->pixelgen-pixelator) (0.4.4)
Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.13/dist-packages (from requests>=2.32.5->pixelgen-pixelator) (3.4.9)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.13/dist-packages (from requests>=2.32.5->pixelgen-pixelator) (3.20)
Requirement already satisfied: urllib3<3,>=1.26 in /usr/local/lib/python3.13/dist-packages (from requests>=2.32.5->pixelgen-pixelator) (2.5.0)
Requirement already satisfied: certifi>=2023.5.7 in /usr/local/lib/python3.13/dist-packages (from requests>=2.32.5->pixelgen-pixelator) (2026.7.22)
Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.13/dist-packages (from plotly->pixelgen-pixelator) (9.1.4)
Requirement already satisfied: fast-array-utils>=1.4 in /usr/local/lib/python3.13/dist-packages (from fast-array-utils[accel,sparse]>=1.4->scanpy->pixelgen-pixelator) (1.5.1)
Requirement already satisfied: joblib in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (1.6.0)
Requirement already satisfied: matplotlib>=3.10 in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (3.10.0)
Requirement already satisfied: networkx>=2.8.8 in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (3.7)
Requirement already satisfied: patsy in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (1.0.3)
Requirement already satisfied: pynndescent>=0.5.13 in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (0.6.0)
Requirement already satisfied: scikit-learn>=1.6 in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (1.6.1)
Requirement already satisfied: seaborn>=0.13.2 in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (0.13.2)
Requirement already satisfied: session-info2 in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (0.4.2)
Requirement already satisfied: statsmodels>=0.14.5 in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (0.15.0)
Requirement already satisfied: tqdm in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (4.67.3)
Requirement already satisfied: umap-learn>=0.5.12 in /usr/local/lib/python3.13/dist-packages (from scanpy->pixelgen-pixelator) (0.5.12)
Requirement already satisfied: isal>=1.6.1 in /usr/local/lib/python3.13/dist-packages (from xopen[zstd]->pixelgen-pixelator) (1.8.0)
Requirement already satisfied: zlib-ng>=0.4.1 in /usr/local/lib/python3.13/dist-packages (from xopen[zstd]->pixelgen-pixelator) (1.0.0)
Requirement already satisfied: backports.zstd in /usr/local/lib/python3.13/dist-packages (from xopen[zstd]->pixelgen-pixelator) (1.8.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.13/dist-packages (from matplotlib>=3.10->scanpy->pixelgen-pixelator) (1.4.0)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.13/dist-packages (from matplotlib>=3.10->scanpy->pixelgen-pixelator) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.13/dist-packages (from matplotlib>=3.10->scanpy->pixelgen-pixelator) (4.66.1)
Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.13/dist-packages (from matplotlib>=3.10->scanpy->pixelgen-pixelator) (1.5.1)
Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.13/dist-packages (from matplotlib>=3.10->scanpy->pixelgen-pixelator) (11.3.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.13/dist-packages (from matplotlib>=3.10->scanpy->pixelgen-pixelator) (3.3.3)
Requirement already satisfied: cloudpickle>=3.0 in /usr/local/lib/python3.13/dist-packages (from joblib->scanpy->pixelgen-pixelator) (3.1.2)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.13/dist-packages (from python-dateutil>=2.8.2->pandas<3.0.0,>=2.0.0->pixelgen-pixelator) (1.17.0)
Requirement already satisfied: threadpoolctl>=3.1.0 in /usr/local/lib/python3.13/dist-packages (from scikit-learn>=1.6->scanpy->pixelgen-pixelator) (3.7.0)
Requirement already satisfied: pydantic-settings in /usr/local/lib/python3.13/dist-packages (from scverse-misc[settings]>=0.1.0->anndata>=0.8.0->pixelgen-pixelator) (2.15.0)
Requirement already satisfied: python-dotenv in /usr/local/lib/python3.13/dist-packages (from scverse-misc[settings]>=0.1.0->anndata>=0.8.0->pixelgen-pixelator) (1.2.3)
Requirement already satisfied: formulaic>=1.1.0 in /usr/local/lib/python3.13/dist-packages (from statsmodels>=0.14.5->scanpy->pixelgen-pixelator) (1.2.2)
Requirement already satisfied: donfig>=0.8 in /usr/local/lib/python3.13/dist-packages (from zarr>=3.1->anndata>=0.8.0->pixelgen-pixelator) (0.8.1.post1)
Requirement already satisfied: google-crc32c>=1.5 in /usr/local/lib/python3.13/dist-packages (from zarr>=3.1->anndata>=0.8.0->pixelgen-pixelator) (1.9.0)
Requirement already satisfied: msgspec>=0.19 in /usr/local/lib/python3.13/dist-packages (from zarr>=3.1->anndata>=0.8.0->pixelgen-pixelator) (0.22.0)
Requirement already satisfied: numcodecs>=0.16 in /usr/local/lib/python3.13/dist-packages (from zarr>=3.1->anndata>=0.8.0->pixelgen-pixelator) (0.17.0)
Requirement already satisfied: pyyaml in /usr/local/lib/python3.13/dist-packages (from donfig>=0.8->zarr>=3.1->anndata>=0.8.0->pixelgen-pixelator) (6.0.3)
Requirement already satisfied: interface-meta>=1.2.0 in /usr/local/lib/python3.13/dist-packages (from formulaic>=1.1.0->statsmodels>=0.14.5->scanpy->pixelgen-pixelator) (2.0.1)
Requirement already satisfied: narwhals>=1.17 in /usr/local/lib/python3.13/dist-packages (from formulaic>=1.1.0->statsmodels>=0.14.5->scanpy->pixelgen-pixelator) (2.26.0)
Requirement already satisfied: wrapt>=1.17.0rc1 in /usr/local/lib/python3.13/dist-packages (from formulaic>=1.1.0->statsmodels>=0.14.5->scanpy->pixelgen-pixelator) (2.5.0)
from pathlib import Path
from pixelator.pna.pixeldataset.download import DownloadableDatasets
DATA_DIR = Path("./pna-analysis")
DATA_DIR.mkdir(parents=True, exist_ok=True)
DownloadableDatasets.download_dataset(
"pna062-unstim-pbmcs",
output_path=DATA_DIR / "PNA062_unstim_PBMCs_1000cells_S02_S2.layout.pxl",
)
File already exists at pna-analysis/PNA062_unstim_PBMCs_1000cells_S02_S2.layout.pxl. Use overwrite=True to download again.
PosixPath('pna-analysis/PNA062_unstim_PBMCs_1000cells_S02_S2.layout.pxl')
This downloads a real PNA dataset: ~1,000 unstimulated PBMCs (peripheral blood mononuclear cells), profiled with a 155-marker immune panel.
The easy way in: pixelator.read_pna
The pixelator package gives you a friendly PixelDataset wrapper:
from pixelator import read_pna as read
pg_data = read(DATA_DIR / "PNA062_unstim_PBMCs_1000cells_S02_S2.layout.pxl")
pg_data
PixelDataset with 1 samples
Mapping the following samples to files:
Sample: PNA062_unstim_PBMCs_1000cells_S02_S2, File: pna-analysis/PNA062_unstim_PBMCs_1000cells_S02_S2.layout.pxl
In total it contains:
1083 components, 158 markers
pg_data = read(DATA_DIR / "PNA062_unstim_PBMCs_1000cells_S02_S2.layout.pxl")
pg_data
PixelDataset with 1 samples
Mapping the following samples to files:
Sample: PNA062_unstim_PBMCs_1000cells_S02_S2, File: pna-analysis/PNA062_unstim_PBMCs_1000cells_S02_S2.layout.pxl
In total it contains:
1083 components, 158 markers
1,083 components (cells) and 158 markers — already more interesting than a flat CSV would be. But to really see what’s inside, let’s drop down a level and query the database directly.
Opening the hood with DuckDB
import duckdb
conn = duckdb.connect(
database=str(DATA_DIR / "PNA062_unstim_PBMCs_1000cells_S02_S2.layout.pxl"),
read_only=True,
config={},
)
conn.sql("SHOW TABLES").show()
┌─────────────────────┐
│ name │
│ varchar │
├─────────────────────┤
│ __adata__X │
│ __adata__obs │
│ __adata__obsm_clr │
│ __adata__obsm_log1p │
│ __adata__uns │
│ __adata__var │
│ edgelist │
│ layouts │
│ metadata │
│ proximity │
└─────────────────────┘
10 rows
That’s the whole file. Ten tables. Let’s go through what each one means.
The four pillars of a PNA .pxl file
Pixelgen’s own documentation groups the contents of a PNA .pxl file into four conceptual pieces. The __adata__* tables you see above are just the on-disk representation of the first one.
1. adata — the cell-by-marker count matrix
The tables prefixed __adata__ (X, obs, var, uns, obsm_clr, obsm_log1p) together store an AnnData object — the standard single-cell data structure used by Scanpy. Think of it as a spreadsheet: one row per cell (component), one column per protein marker, with the count of molecular detections in each cell.
__adata__obs is the cell-level metadata table — one row per component, with QC metrics like UMI counts, edge counts, and marker diversity:
conn.sql("DESCRIBE __adata__obs").show()
conn.sql("SELECT * FROM __adata__obs LIMIT 5").show()
┌────────────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├────────────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ index │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ n_umi1 │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ n_umi2 │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ n_edges │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ reads_in_component │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ n_antibodies │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ n_umi │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ isotype_fraction │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ intracellular_fraction │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ tau_type │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ · │ · │ · │ · │ · │ · │
│ · │ · │ · │ · │ · │ · │
│ · │ · │ · │ · │ · │ · │
│ k_core_1 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_2 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_3 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_4 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_5 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_6 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_7 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ svd_var_expl_s1 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ svd_var_expl_s2 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ svd_var_expl_s3 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
└────────────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
26 rows (20 shown) 6 columns
┌──────────────────┬────────┬────────┬─────────┬────────────────────┬──────────────┬────────┬────────────────────────┬────────────────────────┬──────────┬────────────────────┬────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┬────────────┬────────────────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬─────────────────────┬─────────────────────┬─────────────────────┐
│ index │ n_umi1 │ n_umi2 │ n_edges │ reads_in_component │ n_antibodies │ n_umi │ isotype_fraction │ intracellular_fraction │ tau_type │ tau │ disqualified_for_denoising │ number_of_nodes_removed_in_denoise │ sample │ antibodies │ average_k_core │ k_core_1 │ k_core_2 │ k_core_3 │ k_core_4 │ k_core_5 │ k_core_6 │ k_core_7 │ svd_var_expl_s1 │ svd_var_expl_s2 │ svd_var_expl_s3 │
│ varchar │ uint64 │ uint64 │ uint64 │ uint64 │ uint64 │ uint64 │ double │ double │ varchar │ double │ boolean │ int64 │ varchar │ int64 │ double │ double │ double │ double │ double │ double │ double │ double │ double │ double │ double │
├──────────────────┼────────┼────────┼─────────┼────────────────────┼──────────────┼────────┼────────────────────────┼────────────────────────┼──────────┼────────────────────┼────────────────────────────┼────────────────────────────────────┼──────────────────────────────────────┼────────────┼────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼─────────────────────┼─────────────────────┼─────────────────────┤
│ f1b52a4758932fc7 │ 13355 │ 18311 │ 93749 │ 246004 │ 133 │ 31666 │ 0.00031579612202362154 │ 0.0 │ normal │ 0.9741447474871849 │ false │ 1343 │ PNA062_unstim_PBMCs_1000cells_S02_S2 │ 133 │ 3.377471104654835 │ 5773.0 │ 3009.0 │ 5429.0 │ 8402.0 │ 9053.0 │ 0.0 │ 0.0 │ 0.284416699286014 │ 0.1568092723348554 │ 0.08934063781111805 │
│ eca4983191f3647f │ 17311 │ 23376 │ 105495 │ 273174 │ 139 │ 40687 │ 0.0002212008749723499 │ 0.0 │ normal │ 0.9753554408442155 │ false │ 2072 │ PNA062_unstim_PBMCs_1000cells_S02_S2 │ 139 │ 2.9251849485093517 │ 7919.0 │ 5180.0 │ 9614.0 │ 17974.0 │ 0.0 │ 0.0 │ 0.0 │ 0.25355749146183176 │ 0.19298127826371939 │ 0.14643339434067068 │
│ f98240c66b2e49fc │ 11827 │ 14934 │ 78611 │ 225033 │ 158 │ 26761 │ 0.0017936549456298344 │ 0.0 │ normal │ 0.9468123934534218 │ false │ 1545 │ PNA062_unstim_PBMCs_1000cells_S02_S2 │ 158 │ 3.3730802286910055 │ 4706.0 │ 3152.0 │ 3553.0 │ 8152.0 │ 7198.0 │ 0.0 │ 0.0 │ 0.2395866519379636 │ 0.1878859650657518 │ 0.12070936179127005 │
│ b5f8b192a0cc2603 │ 21450 │ 28111 │ 97857 │ 296160 │ 158 │ 49561 │ 0.0004035431084925647 │ 0.0 │ normal │ 0.9495831056082813 │ false │ 3489 │ PNA062_unstim_PBMCs_1000cells_S02_S2 │ 158 │ 2.3116159883779583 │ 12780.0 │ 12920.0 │ 19498.0 │ 4363.0 │ 0.0 │ 0.0 │ 0.0 │ 0.34932608817161837 │ 0.23720261583286217 │ 0.1163322605159803 │
│ 7994e60e303c6dbb │ 15767 │ 20905 │ 126994 │ 357256 │ 144 │ 36672 │ 0.0002181500872600349 │ 0.0 │ normal │ 0.9520864572339557 │ false │ 1715 │ PNA062_unstim_PBMCs_1000cells_S02_S2 │ 144 │ 3.855802792321117 │ 5937.0 │ 2389.0 │ 3245.0 │ 7657.0 │ 14342.0 │ 3102.0 │ 0.0 │ 0.29226362506389475 │ 0.1730780968717049 │ 0.140348889354665 │
└──────────────────┴────────┴────────┴─────────┴────────────────────┴──────────────┴────────┴────────────────────────┴────────────────────────┴──────────┴────────────────────┴────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┴────────────┴────────────────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴─────────────────────┴─────────────────────┴─────────────────────┘
# List of key tables to inspect
tables_to_inspect = ['edgelist', 'layouts', 'metadata', 'proximity', '__adata__obs']
for table in tables_to_inspect:
print(f"\n=== Structure of table: {table} ===")
try:
# Describe table columns and types
conn.sql(f"DESCRIBE {table}").show()
# Preview the first few records
print(f"\n--- Preview of table: {table} ---")
conn.sql(f"SELECT * FROM {table} LIMIT 5").show()
except Exception as e:
print(f"Error inspecting table {table}: {e}")
=== Structure of table: edgelist ===
┌──────────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├──────────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ marker_1 │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ marker_2 │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ umi1 │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ umi2 │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ read_count │ UINTEGER │ YES │ NULL │ NULL │ NULL │
│ uei_count │ USMALLINT │ YES │ NULL │ NULL │ NULL │
│ corrected_read_count │ UINTEGER │ YES │ NULL │ NULL │ NULL │
│ component │ VARCHAR │ YES │ NULL │ NULL │ NULL │
└──────────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
--- Preview of table: edgelist ---
┌──────────┬──────────┬──────────────────┬───────────────────┬────────────┬───────────┬──────────────────────┬──────────────────┐
│ marker_1 │ marker_2 │ umi1 │ umi2 │ read_count │ uei_count │ corrected_read_count │ component │
│ varchar │ varchar │ uint64 │ uint64 │ uint32 │ uint16 │ uint32 │ varchar │
├──────────┼──────────┼──────────────────┼───────────────────┼────────────┼───────────┼──────────────────────┼──────────────────┤
│ CD29 │ CD43 │ 555638967335511 │ 28226901571890329 │ 9 │ 2 │ 0 │ 8cd2f4585f9102dd │
│ B2M │ CD44 │ 1592993435260922 │ 33899519959851077 │ 3 │ 2 │ 0 │ 8cd2f4585f9102dd │
│ B2M │ CD5 │ 1772702303642965 │ 23228591926107523 │ 6 │ 2 │ 1 │ 8cd2f4585f9102dd │
│ CD45 │ CD45 │ 1905030979812053 │ 14976911110869850 │ 3 │ 2 │ 0 │ 8cd2f4585f9102dd │
│ CD82 │ CD82 │ 1927887423101473 │ 8679240914900276 │ 1 │ 1 │ 0 │ 8cd2f4585f9102dd │
└──────────┴──────────┴──────────────────┴───────────────────┴────────────┴───────────┴──────────────────────┴──────────────────┘
=== Structure of table: layouts ===
┌──────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├──────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ index │ BIGINT │ YES │ NULL │ NULL │ NULL │
│ pixel_type │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ x │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ y │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ z │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ component │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ graph_projection │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ layout │ VARCHAR │ YES │ NULL │ NULL │ NULL │
└──────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
--- Preview of table: layouts ---
┌───────────────────┬────────────┬─────────────────────┬─────────────────────┬─────────────────────┬──────────────────┬──────────────────┬──────────┐
│ index │ pixel_type │ x │ y │ z │ component │ graph_projection │ layout │
│ int64 │ varchar │ double │ double │ double │ varchar │ varchar │ varchar │
├───────────────────┼────────────┼─────────────────────┼─────────────────────┼─────────────────────┼──────────────────┼──────────────────┼──────────┤
│ 10314868810782020 │ A │ -100584836.53538795 │ 91677767.86083965 │ -5191661.072215953 │ ea0c2d7369f4a0b8 │ full │ wpmds_3d │
│ 42707597301049753 │ A │ -67487283.7653745 │ 10617775.68554159 │ 66592152.03392912 │ ea0c2d7369f4a0b8 │ full │ wpmds_3d │
│ 33308222300841369 │ A │ 83884538.08957906 │ -34694762.045810506 │ -32016604.700219292 │ ea0c2d7369f4a0b8 │ full │ wpmds_3d │
│ 34004950245090693 │ A │ -104504573.20151494 │ 39021873.54138253 │ -29411711.614754535 │ ea0c2d7369f4a0b8 │ full │ wpmds_3d │
│ 39479084284462619 │ A │ -28051729.99806844 │ 45273442.40727851 │ 54509380.288791195 │ ea0c2d7369f4a0b8 │ full │ wpmds_3d │
└───────────────────┴────────────┴─────────────────────┴─────────────────────┴─────────────────────┴──────────────────┴──────────────────┴──────────┘
=== Structure of table: metadata ===
┌─────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├─────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ value │ JSON │ YES │ NULL │ NULL │ NULL │
└─────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
--- Preview of table: metadata ---
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ value │
│ json │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ {"sample_name":"PNA062_unstim_PBMCs_1000cells_S02_S2","version":"0.21.2+dirty","technology":"single-cell-pna","panel_name":"proxiome-immuno-155","panel_version":"1.0.0"} │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
=== Structure of table: proximity ===
┌──────────────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├──────────────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ marker_1 │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ marker_2 │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ join_count │ UINTEGER │ YES │ NULL │ NULL │ NULL │
│ join_count_expected_mean │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ join_count_expected_sd │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ join_count_z │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ join_count_p │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ component │ VARCHAR │ YES │ NULL │ NULL │ NULL │
└──────────────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
--- Preview of table: proximity ---
┌──────────┬──────────┬────────────┬──────────────────────────┬────────────────────────┬───────────────────┬─────────────────────┬──────────────────┐
│ marker_1 │ marker_2 │ join_count │ join_count_expected_mean │ join_count_expected_sd │ join_count_z │ join_count_p │ component │
│ varchar │ varchar │ uint32 │ double │ double │ double │ double │ varchar │
├──────────┼──────────┼────────────┼──────────────────────────┼────────────────────────┼───────────────────┼─────────────────────┼──────────────────┤
│ CD33 │ CD33 │ 0 │ 0.25 │ 0.5198095999517709 │ -0.25 │ 0.4012936743170763 │ 91984005700471b2 │
│ CD33 │ CD59 │ 5 │ 3.02 │ 1.9590762613996928 │ 1.010680410463122 │ 0.15608470795325824 │ 91984005700471b2 │
│ CD33 │ CD45RB │ 0 │ 0.08 │ 0.2726599243442907 │ -0.08 │ 0.4681186279860126 │ 91984005700471b2 │
│ CD33 │ KLRG1 │ 1 │ 0.14 │ 0.34873508801977704 │ 0.86 │ 0.1948945212518084 │ 91984005700471b2 │
│ CD33 │ VISTA │ 0 │ 0.2 │ 0.5124707431905383 │ -0.2 │ 0.42074029056089696 │ 91984005700471b2 │
└──────────┴──────────┴────────────┴──────────────────────────┴────────────────────────┴───────────────────┴─────────────────────┴──────────────────┘
=== Structure of table: __adata__obs ===
┌────────────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├────────────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ index │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ n_umi1 │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ n_umi2 │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ n_edges │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ reads_in_component │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ n_antibodies │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ n_umi │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ isotype_fraction │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ intracellular_fraction │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ tau_type │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ · │ · │ · │ · │ · │ · │
│ · │ · │ · │ · │ · │ · │
│ · │ · │ · │ · │ · │ · │
│ k_core_1 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_2 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_3 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_4 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_5 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_6 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ k_core_7 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ svd_var_expl_s1 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ svd_var_expl_s2 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ svd_var_expl_s3 │ DOUBLE │ YES │ NULL │ NULL │ NULL │
└────────────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
26 rows (20 shown) 6 columns
--- Preview of table: __adata__obs ---
┌──────────────────┬────────┬────────┬─────────┬────────────────────┬──────────────┬────────┬────────────────────────┬────────────────────────┬──────────┬────────────────────┬────────────────────────────┬────────────────────────────────────┬──────────────────────────────────────┬────────────┬────────────────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬─────────────────────┬─────────────────────┬─────────────────────┐
│ index │ n_umi1 │ n_umi2 │ n_edges │ reads_in_component │ n_antibodies │ n_umi │ isotype_fraction │ intracellular_fraction │ tau_type │ tau │ disqualified_for_denoising │ number_of_nodes_removed_in_denoise │ sample │ antibodies │ average_k_core │ k_core_1 │ k_core_2 │ k_core_3 │ k_core_4 │ k_core_5 │ k_core_6 │ k_core_7 │ svd_var_expl_s1 │ svd_var_expl_s2 │ svd_var_expl_s3 │
│ varchar │ uint64 │ uint64 │ uint64 │ uint64 │ uint64 │ uint64 │ double │ double │ varchar │ double │ boolean │ int64 │ varchar │ int64 │ double │ double │ double │ double │ double │ double │ double │ double │ double │ double │ double │
├──────────────────┼────────┼────────┼─────────┼────────────────────┼──────────────┼────────┼────────────────────────┼────────────────────────┼──────────┼────────────────────┼────────────────────────────┼────────────────────────────────────┼──────────────────────────────────────┼────────────┼────────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼─────────────────────┼─────────────────────┼─────────────────────┤
│ f1b52a4758932fc7 │ 13355 │ 18311 │ 93749 │ 246004 │ 133 │ 31666 │ 0.00031579612202362154 │ 0.0 │ normal │ 0.9741447474871849 │ false │ 1343 │ PNA062_unstim_PBMCs_1000cells_S02_S2 │ 133 │ 3.377471104654835 │ 5773.0 │ 3009.0 │ 5429.0 │ 8402.0 │ 9053.0 │ 0.0 │ 0.0 │ 0.284416699286014 │ 0.1568092723348554 │ 0.08934063781111805 │
│ eca4983191f3647f │ 17311 │ 23376 │ 105495 │ 273174 │ 139 │ 40687 │ 0.0002212008749723499 │ 0.0 │ normal │ 0.9753554408442155 │ false │ 2072 │ PNA062_unstim_PBMCs_1000cells_S02_S2 │ 139 │ 2.9251849485093517 │ 7919.0 │ 5180.0 │ 9614.0 │ 17974.0 │ 0.0 │ 0.0 │ 0.0 │ 0.25355749146183176 │ 0.19298127826371939 │ 0.14643339434067068 │
│ f98240c66b2e49fc │ 11827 │ 14934 │ 78611 │ 225033 │ 158 │ 26761 │ 0.0017936549456298344 │ 0.0 │ normal │ 0.9468123934534218 │ false │ 1545 │ PNA062_unstim_PBMCs_1000cells_S02_S2 │ 158 │ 3.3730802286910055 │ 4706.0 │ 3152.0 │ 3553.0 │ 8152.0 │ 7198.0 │ 0.0 │ 0.0 │ 0.2395866519379636 │ 0.1878859650657518 │ 0.12070936179127005 │
│ b5f8b192a0cc2603 │ 21450 │ 28111 │ 97857 │ 296160 │ 158 │ 49561 │ 0.0004035431084925647 │ 0.0 │ normal │ 0.9495831056082813 │ false │ 3489 │ PNA062_unstim_PBMCs_1000cells_S02_S2 │ 158 │ 2.3116159883779583 │ 12780.0 │ 12920.0 │ 19498.0 │ 4363.0 │ 0.0 │ 0.0 │ 0.0 │ 0.34932608817161837 │ 0.23720261583286217 │ 0.1163322605159803 │
│ 7994e60e303c6dbb │ 15767 │ 20905 │ 126994 │ 357256 │ 144 │ 36672 │ 0.0002181500872600349 │ 0.0 │ normal │ 0.9520864572339557 │ false │ 1715 │ PNA062_unstim_PBMCs_1000cells_S02_S2 │ 144 │ 3.855802792321117 │ 5937.0 │ 2389.0 │ 3245.0 │ 7657.0 │ 14342.0 │ 3102.0 │ 0.0 │ 0.29226362506389475 │ 0.1730780968717049 │ 0.140348889354665 │
└──────────────────┴────────┴────────┴─────────┴────────────────────┴──────────────┴────────┴────────────────────────┴────────────────────────┴──────────┴────────────────────┴────────────────────────────┴────────────────────────────────────┴──────────────────────────────────────┴────────────┴────────────────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴─────────────────────┴─────────────────────┴─────────────────────┘
Some of the more useful columns here:
| Column | What it tells you |
|---|---|
n_umi1, n_umi2 | Unique molecular identifiers seen on each side of the antibody pair |
n_edges | Number of edges in this cell’s spatial graph |
n_antibodies | Number of distinct markers detected |
isotype_fraction | Fraction of signal from isotype control antibodies (a background indicator) |
tau_type / tau | A measure of how evenly molecules are distributed across markers |
k_core_1 … k_core_7 | Graph k-core decomposition sizes — describe network topology |
svd_var_expl_s1 … s3 | Variance explained by the top singular vectors of the cell’s graph |
Note: there’s no literal molecule_count or unique_markers column — the real names are n_umi (total UMI count) and n_antibodies (unique marker count). It’s an easy mix-up if you’re used to other platforms’ naming conventions.
2. edgelist — the raw spatial network
This is the heart of PNA data: every row is an edge between two antibody-DNA conjugates (markers) that were found close enough together on a cell surface to be linked via their unique molecular identifiers (UMIs).
conn.sql("DESCRIBE edgelist").show()
conn.sql("SELECT * FROM edgelist LIMIT 5").show()
┌──────────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├──────────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ marker_1 │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ marker_2 │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ umi1 │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ umi2 │ UBIGINT │ YES │ NULL │ NULL │ NULL │
│ read_count │ UINTEGER │ YES │ NULL │ NULL │ NULL │
│ uei_count │ USMALLINT │ YES │ NULL │ NULL │ NULL │
│ corrected_read_count │ UINTEGER │ YES │ NULL │ NULL │ NULL │
│ component │ VARCHAR │ YES │ NULL │ NULL │ NULL │
└──────────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
┌──────────┬──────────┬──────────────────┬───────────────────┬────────────┬───────────┬──────────────────────┬──────────────────┐
│ marker_1 │ marker_2 │ umi1 │ umi2 │ read_count │ uei_count │ corrected_read_count │ component │
│ varchar │ varchar │ uint64 │ uint64 │ uint32 │ uint16 │ uint32 │ varchar │
├──────────┼──────────┼──────────────────┼───────────────────┼────────────┼───────────┼──────────────────────┼──────────────────┤
│ CD29 │ CD43 │ 555638967335511 │ 28226901571890329 │ 9 │ 2 │ 0 │ 8cd2f4585f9102dd │
│ B2M │ CD44 │ 1592993435260922 │ 33899519959851077 │ 3 │ 2 │ 0 │ 8cd2f4585f9102dd │
│ B2M │ CD5 │ 1772702303642965 │ 23228591926107523 │ 6 │ 2 │ 1 │ 8cd2f4585f9102dd │
│ CD45 │ CD45 │ 1905030979812053 │ 14976911110869850 │ 3 │ 2 │ 0 │ 8cd2f4585f9102dd │
│ CD82 │ CD82 │ 1927887423101473 │ 8679240914900276 │ 1 │ 1 │ 0 │ 8cd2f4585f9102dd │
└──────────┴──────────┴──────────────────┴───────────────────┴────────────┴───────────┴──────────────────────┴──────────────────┘
A few important things to know about this table:
- It’s organized by component — no edges ever span across two different cells.
- Because each UMI can only pair with one specific partner, a PNA component graph is a simple, undirected bipartite graph — and since edges only form between molecules that were spatially close together, it’s also a spatial graph.
- This table can get huge — over 100 million rows isn’t unusual for a full experiment — so direct manipulation in pandas is often impractical. SQL aggregation (as below) or purpose-built tools like
pixelatorR’s graph utilities are the way to go.
3. layouts — precomputed spatial coordinates
conn.sql("DESCRIBE layouts").show()
conn.sql("SELECT * FROM layouts LIMIT 5").show()
┌──────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├──────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ index │ BIGINT │ YES │ NULL │ NULL │ NULL │
│ pixel_type │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ x │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ y │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ z │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ component │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ graph_projection │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ layout │ VARCHAR │ YES │ NULL │ NULL │ NULL │
└──────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
┌───────────────────┬────────────┬─────────────────────┬─────────────────────┬─────────────────────┬──────────────────┬──────────────────┬──────────┐
│ index │ pixel_type │ x │ y │ z │ component │ graph_projection │ layout │
│ int64 │ varchar │ double │ double │ double │ varchar │ varchar │ varchar │
├───────────────────┼────────────┼─────────────────────┼─────────────────────┼─────────────────────┼──────────────────┼──────────────────┼──────────┤
│ 10314868810782020 │ A │ -100584836.53538795 │ 91677767.86083965 │ -5191661.072215953 │ ea0c2d7369f4a0b8 │ full │ wpmds_3d │
│ 42707597301049753 │ A │ -67487283.7653745 │ 10617775.68554159 │ 66592152.03392912 │ ea0c2d7369f4a0b8 │ full │ wpmds_3d │
│ 33308222300841369 │ A │ 83884538.08957906 │ -34694762.045810506 │ -32016604.700219292 │ ea0c2d7369f4a0b8 │ full │ wpmds_3d │
│ 34004950245090693 │ A │ -104504573.20151494 │ 39021873.54138253 │ -29411711.614754535 │ ea0c2d7369f4a0b8 │ full │ wpmds_3d │
│ 39479084284462619 │ A │ -28051729.99806844 │ 45273442.40727851 │ 54509380.288791195 │ ea0c2d7369f4a0b8 │ full │ wpmds_3d │
└───────────────────┴────────────┴─────────────────────┴─────────────────────┴─────────────────────┴──────────────────┴──────────────────┴──────────┘
Pixelator applies graph-layout algorithms (here, weighted multidimensional scaling, wpmds_3d) to each component’s graph, producing 2D or 3D coordinates for every pixel in the network. This is what lets you reconstruct and visualize the spatial arrangement of antibodies on a cell’s surface — not just that two markers were close, but a geometric model of the whole surface.
4. proximity — spatial co-localization statistics
conn.sql("DESCRIBE proximity").show()
conn.sql("SELECT * FROM proximity LIMIT 5").show()
┌──────────────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├──────────────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ marker_1 │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ marker_2 │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ join_count │ UINTEGER │ YES │ NULL │ NULL │ NULL │
│ join_count_expected_mean │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ join_count_expected_sd │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ join_count_z │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ join_count_p │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ component │ VARCHAR │ YES │ NULL │ NULL │ NULL │
└──────────────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
┌──────────┬──────────┬────────────┬──────────────────────────┬────────────────────────┬───────────────────┬─────────────────────┬──────────────────┐
│ marker_1 │ marker_2 │ join_count │ join_count_expected_mean │ join_count_expected_sd │ join_count_z │ join_count_p │ component │
│ varchar │ varchar │ uint32 │ double │ double │ double │ double │ varchar │
├──────────┼──────────┼────────────┼──────────────────────────┼────────────────────────┼───────────────────┼─────────────────────┼──────────────────┤
│ CD33 │ CD33 │ 0 │ 0.25 │ 0.5198095999517709 │ -0.25 │ 0.4012936743170763 │ 91984005700471b2 │
│ CD33 │ CD59 │ 5 │ 3.02 │ 1.9590762613996928 │ 1.010680410463122 │ 0.15608470795325824 │ 91984005700471b2 │
│ CD33 │ CD45RB │ 0 │ 0.08 │ 0.2726599243442907 │ -0.08 │ 0.4681186279860126 │ 91984005700471b2 │
│ CD33 │ KLRG1 │ 1 │ 0.14 │ 0.34873508801977704 │ 0.86 │ 0.1948945212518084 │ 91984005700471b2 │
│ CD33 │ VISTA │ 0 │ 0.2 │ 0.5124707431905383 │ -0.2 │ 0.42074029056089696 │ 91984005700471b2 │
└──────────┴──────────┴────────────┴──────────────────────────┴────────────────────────┴───────────────────┴─────────────────────┴──────────────────┘
This table quantifies, per cell, how clustered or co-localized pairs of markers are on the cell surface, compared to what you’d expect under a random distribution. It’s computed with a join-count spatial statistic: join_count_z and join_count_p tell you whether two markers (or a marker with itself) are found together more or less often than chance. In PNA, this table replaces the separate polarization and colocalization scores used in Pixelgen’s earlier MPX assay — proximity now captures both self-clustering (marker_1 == marker_2) and pairwise co-localization in one unified statistic.
Bonus: metadata
conn.sql("SELECT * FROM metadata").show()
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ value │
│ json │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ {"sample_name":"PNA062_unstim_PBMCs_1000cells_S02_S2","version":"0.21.2+dirty","technology":"single-cell-pna","panel_name":"proxiome-immuno-155","panel_version":"1.0.0"} │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
A small JSON blob recording provenance: which sample this is, which Pixelator version produced it, and which antibody panel was used. Always worth checking when you’re troubleshooting or comparing files generated at different times.
Putting it together: two quick queries
QC check — UMI and marker distributions per cell. Remember, the correct column names are n_umi and n_antibodies, not molecule_count / unique_markers:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
obs_df = conn.sql("""
SELECT
n_umi AS total_umis,
n_antibodies AS unique_markers
FROM __adata__obs
""").df()
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
sns.histplot(obs_df['total_umis'], bins=50, ax=axes[0], color='skyblue', kde=True)
axes[0].set_title('Distribution of UMIs per Component (Cell)')
axes[0].set_xlabel('Total UMI Count')
sns.histplot(obs_df['unique_markers'], bins=30, ax=axes[1], color='salmon', kde=True)
axes[1].set_title('Distribution of Unique Surface Markers per Component')
axes[1].set_xlabel('Unique Markers Count')
plt.tight_layout()
plt.show()

plt.figure(figsize=(7, 6))
sns.scatterplot(data=obs_df, x='total_umis',
y='unique_markers',
alpha=0.5, color='teal')
sns.regplot(data=obs_df, x='total_umis',
y='unique_markers', scatter=False,
color='darkred', ci=None)
plt.title('Total UMIs vs. Unique Markers per Component')
plt.xlabel('Total UMI Count')
plt.ylabel('Unique Markers Count')
plt.tight_layout()
plt.show()

Most abundant markers — aggregate straight over edgelist:
marker_counts = conn.sql("""
SELECT
marker_1 AS marker,
COUNT(*) AS total_edges
FROM edgelist
GROUP BY marker_1
ORDER BY total_edges DESC
LIMIT 15
""").df()
plt.figure(figsize=(10, 6))
sns.barplot(data=marker_counts, x='total_edges', y='marker', hue='marker',
palette='viridis', legend=False)
plt.title('Top 15 Most Abundant Surface Markers in Dataset')
plt.xlabel('Edge (Observation) Count')
plt.ylabel('Marker Name')
plt.show()

Because DuckDB pushes the aggregation down into the database engine, this works even when edgelist has tens or hundreds of millions of rows — you never have to load the whole table into memory just to count markers.
From raw tables to real analysis
Querying the .pxl file by hand is great for understanding the format (and for ad hoc debugging), but for real analysis workflows, you’ll generally want to let the official libraries do the heavy lifting:
- Python:
pixelator’sread_pna()loads the file into aPixelDataset, from which you can pull a ready-to-useAnnDataobject with.adata()— directly compatible with Scanpy for clustering, UMAP, and cell-type annotation. - R:
pixelatorR’sReadPNA_Seurat()builds a Seurat object with a specializedPNAAssay5that stores abundance data alongside spatial metrics and cell graphs — note that the edgelist itself isn’t duplicated into the Seurat object; it’s loaded on demand from the original.pxlfile viaLoadCellGraphs().
Both tools layer the same four building blocks — adata, edgelist, layouts, and proximity — on top of the mainstream single-cell ecosystem, so your existing clustering and annotation skills transfer directly. The spatial and graph layers are what’s new, and now you know exactly where they live.
Summary
A .pxl file isn’t a black box — it’s a DuckDB database with four main ingredients:
adata— standard cell × marker abundance data, ready for Scanpy/Seuratedgelist— the raw bipartite spatial graph of antibody-antibody adjacencies, one cell’s worth of edges at a timelayouts— precomputed 2D/3D coordinates for visualizing each cell’s surface networkproximity— per-cell statistics on marker self-clustering and co-localization, replacing MPX’s old polarization/colocalization scores
Once you know those four pieces, you can query a .pxl file with plain SQL to answer almost any question before you ever load a full analysis pipeline — and you’ll understand exactly what pixelator and pixelatorR are doing for you under the hood.
