Skip to main content

jqscore

Tests PyPI Python 3.10+

JAX reimplementation of Q-score — a per-atom cryo-EM map-to-model fit metric.

Q-score measures how well each atom in an atomic model fits into a cryo-EM density map by comparing the observed radial density profile around each atom to a theoretical Gaussian profile (Pearson correlation).

Pintilie et al., Nature Methods 2020. Reference implementation: gregdp/mapq

Installation

pip install jqscore            # CPU
pip install "jqscore[gpu]"     # NVIDIA GPU: CUDA 13 build of JAX, driver ≥ 580

With an older driver (≥ 525), use the CUDA 12 build of JAX: pip install jqscore "jax[cuda12]". The development version: pip install git+https://github.com/aozalevsky/jqscore.

Python API

import jqscore

# From file paths (PDB/mmCIF + MRC/CCP4)
result = jqscore.run("structure.cif", "map.mrc")

# Access results
print(result.atom_qscores)          # per-atom Q-scores (H: nearest heavy atom's in the residue)
print(jqscore.chain_qscores(result))    # per-chain averages
print(jqscore.residue_qscores(result))  # per-residue breakdown

# Lower-level: supply parsed atoms and map arrays
atoms = jqscore.read_cif("structure.cif")
data, origin, voxel_size = jqscore.read_mrc("map.mrc")
result = jqscore.compute_qscores(atoms, data, origin, voxel_size, sigma=0.4)

# A document already parsed with gemmi (import gemmi): one Q-score per _atom_site row,
# written back into the same document (no second read, no per-atom objects)
doc = gemmi.cif.read("structure.cif")
q = jqscore.block_qscores(doc[0], data, origin, voxel_size, sigma=0.4)
jqscore.add_qscore_column(doc[0], q)
doc.write_file("structure.cif__Q__map.mrc.cif")

# Or atoms as arrays: positions, hydrogen flags and residue codes
q, min_d, max_d = jqscore.qscores_from_arrays(coords, heavy, residue, data, origin, voxel_size)

Computation modes

Mode What it does
use_retry=True (default) MapQ's retry loop: up to 50 attempts with 8, 10, 12, … sphere points per shell, until ≥ 8 are clear of other atoms. Matches a float64 reference to ~1e-5, except for 10 of the 750,165 atoms in the benchmark with a sample point within ~1e-7 Å of the exclusion cutoff, which float32 cannot decide.
use_retry=False A fixed budget of n_gen points per shell; all clear points count. ~0.03 mean Q-score difference from retry mode.
# Fast mode
result = jqscore.run("structure.cif", "map.mrc", use_retry=False)

# Check JAX backend
print(jqscore.backend())  # 'cpu', 'gpu', or 'tpu'

Atoms are processed in batches (batch_size, default 32768); the map stays on the GPU throughout. Lower batch_size if the GPU runs out of memory.

On an RTX A4000, jqscore 36ZA.cif emd_76130.map (11.2 M atoms, 2888 chains, 800³ map) takes 1 min 40 s, over half of it reading the 1.2 GB mmCIF. jqscore-cmd, which reads the mmCIF once and writes the annotated copy, takes 1 min 15 s (14 GB peak memory).

CLI

jqscore structure.cif map.mrc [options]

options:
  --sigma FLOAT       Gaussian sigma in Å (default 0.4; use 0.6 for older datasets)
  --num-pts INT       Sample points per shell (default 8)
  --to-rad FLOAT      Max radial distance in Å
  --d-rad FLOAT       Radial shell spacing in Å
  --no-retry          Use fast fixed-budget mode
  --max-retries INT   Max retry attempts per shell (default 50)
  --n-gen INT         Sphere-point budget for --no-retry mode
  --no-mapq-compat    Interpolate the map directly instead of replicating
                      MapQ's trimmed-map resampling
  --batch-size INT    Atoms per batch (default 32768; lower for small GPUs)
  --verbose           Print progress and per-atom Q-scores
  --output FILE       Write per-residue TSV to file (default: stdout summary)

MapQ-compatibility mode

The jqscore-cmd entry point matches the argument style of the original MapQ Chimera plugin’s mapq_cmd.py, so tools like the VA validation package can use jqscore as a drop-in replacement without changes.

How to run:

jqscore-cmd <chimera_root> map=<mapfile> cif=<cif1> [cif=<cif2> ...] [np=<n>] [res=<resolution>] [sigma=<sigma>]
  • Required: map=<path> — path to the MRC/CCP4 density map.
  • Required: cif=<path> — path to an mmCIF structure; repeat for multiple models.
  • Optional: first positional argument is the Chimera root path (accepted but unused; jqscore does not require Chimera).

All parameters other than the map path, structure path(s), and sigma are accepted for compatibility but ignored (e.g. np=, res=, Chimera root). The optional sigma=<float> sets the Gaussian width in Å (default 0.4). Output CIFs with a Q-score column are written to the current working directory as {cif_basename}__Q__{map_basename}.cif, matching the naming expected by VA.

Container

container/jqscore.def builds an Apptainer/Singularity image with the CUDA build of JAX (the host needs only an NVIDIA driver, ≥ 580 for CUDA 13). From the repository root:

apptainer build --fakeroot jqscore.sif container/jqscore.def
# driver older than 580: --build-arg JAX="jax[cuda12]==0.9.2"

apptainer run --nv jqscore.sif structure.cif map.mrc
apptainer exec --nv jqscore.sif jqscore-cmd /opt/chimera map=map.mrc cif=structure.cif

The build ends by running the test suite on the CPU. apptainer run-help jqscore.sif lists the commands.

Benchmarking against MapQ

The included benchmark script (needs pip install "jqscore[benchmark]" for matplotlib) downloads real PDB+EMDB structures, runs both the original MapQ (via Singularity) and jqscore, and produces per-atom scatter plots, a summary comparison, and a markdown report.

# Run with default structures (7 entries):
python -m jqscore.benchmark

# Run on specific PDB IDs:
python -m jqscore.benchmark 9XZK 9UX2

# Custom output directory:
python -m jqscore.benchmark -o my_benchmark

# After changing jqscore: re-run it only, keeping MapQ results and times
python -m jqscore.benchmark --rerun-jqscore

The MapQ reference is invoked via the MAPQ_CMD environment variable (expects a Singularity container by default). A full benchmark run with all 7 default structures takes approximately 30 minutes.

Results are saved to jqscore_mapq_benchmark/ (plots, output CIFs, and README.md); the latest run is in docs/jqscore_vs_mapq/. Of the 750,165 non-H atoms of its 7 entries, 15 differ from MapQ by more than 1e-3: 5 in 3J7O where the density around the atom is flat (MapQ gives ~0.1, jqscore 0), and 10 with a sample point within ~1e-7 Å of the exclusion cutoff, a tie that MapQ also breaks differently from a float64 reference in 3 of the 10. See spec/benchmark.md for the full specification.

License

MIT

Metadata

Release files for jqscore 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jqscore 0.3.0
File Size Uploaded Provenance Actions
jqscore-0.3.0.tar.gz 950.0 kB With provenance Details for jqscore-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for jqscore 0.3.0
File Interpreter ABI Platform Actions
jqscore-0.3.0-py3-none-any.whl Python 3 none any Details for jqscore-0.3.0-py3-none-any.whl

Total release size: 993.0 kB

Release files / jqscore-0.3.0.tar.gz

Download URL jqscore-0.3.0.tar.gz
Size 950.0 kB
Tags Source
SHA-256 checksum
How to use checksums
1da66c57dba370c961f1321e213cb90193b9b705d0940c48d3d5df1b8f06a908
BLAKE2b-256 checksum
How to use checksums
001746dda7411eafe37a430a19839fc9121502c2d5367e0904830650f43c3c68
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

Release files / jqscore-0.3.0-py3-none-any.whl

Download URL jqscore-0.3.0-py3-none-any.whl
Size 43.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d625b25286ce52cc5f68b7688ab40346c4ca8e29922b31f4b6636ab96dfb08c0
BLAKE2b-256 checksum
How to use checksums
e54475ffcc0ca5be1034a545ca579c07f4d4267bbba9017ad229f69426a99902
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

Security for jqscore 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for jqscore-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page