Skip to content

Structure QC: is this structure ML-ready?

Before a PDB or mmCIF file becomes a descriptor table, contact map, or graph, it is worth asking whether the coordinates are trustworthy: are residues missing, is the chain broken, are there alternate conformations, what is the net charge? prepare_structure reads the file once and answers all of that in a single StructureReport.

import molscope as ms

report = ms.prepare_structure("1ubq.pdb")        # a path or a 4-char PDB id
print(report.summary())
# 1ubq.pdb: ML-ready | 660 atoms | chains A | net charge +0 | warnings: ...

What it checks

Check Severity Needs
Missing backbone atoms (N, CA, C, O) blocker core
Backbone chain breaks (CA–CA > 4.5 Å between adjacent residues) blocker core
Residue-numbering gaps warning core
Truncated side chains (fewer heavy atoms than the residue should have) warning core
Non-standard residues + ligand / water inventory warning core
Hydrogens present warning core
Alternate conformations / partial occupancies warning core (PDB)
Net formal charge at a chosen pH informational chem (+ propka for "pka")

The topology checks run on the bare NumPy install. The net-charge step reuses the protonation backends and degrades gracefully: if RDKit (or PROPKA) is missing, or the file cannot be template-parsed, the charge is reported as None with an explanatory note instead of raising.

report = ms.prepare_structure("1ubq.pdb", protonation="pka", ph=7.4)
report.net_charge        # e.g. 0     (PROPKA prediction at pH 7.4)
report.ml_ready          # True
report.blockers          # []  -> nothing that corrupts distance/graph features
report.warnings          # ['57 atom(s) with occupancy < 1', 'no hydrogens present']
report.to_dict()         # JSON-serialisable, for pipelines
print(report.report_markdown())   # a full human-readable report

ml_ready is a heuristic

A structure is called not ML-ready only for blockers — missing backbone atoms or internal chain breaks, which corrupt distance- and graph-based features. Everything else is surfaced as a warning. Treat the verdict as triage, not as a guarantee for a specific modelling task.

From the command line

molscope structure-report 1ubq.pdb                  # one-line verdict
molscope structure-report --fetch 1ubq --json       # full JSON report
molscope structure-report 1ubq.pdb --out report.md  # write a Markdown report
molscope structure-report model.pdb --protonation pka --ph 7.4

--fetch <PDBID> downloads (and caches) the entry from RCSB first. Gzipped PDBs (.pdb.gz, as RCSB serves them) are handled transparently, including for the net-charge step.

A faster, format-agnostic check: quality_report

prepare_structure is protein-shaped: it reasons about backbones, chain breaks, and protonation. Sometimes you want a cheaper, upstream question answered first: did this file parse into something sensible at all? — and you want it to work on an .xyz or .sdf small molecule just as well as on a protein. That is quality_report (molscope qc).

import molscope as ms

report = ms.quality_report("3ptb.pdb")     # a path, a PDB id, or a Molecule
print(report.summary())
# 3ptb.pdb: clean | 1701 atoms | chains A | 1 ligand(s) | bonds explicit (21)

It reads the structure once and reports an inventory plus a few parse-fidelity signals:

Field What it tells you
n_atoms, chains, n_residues basic size of what was parsed
ligands, n_waters, n_ions, n_hetero_atoms the HETATM split
missing_metadata per-atom fields the format did not carry (e.g. an XYZ has no chains or residue names)
unknown_elements, blank_elements atom symbols that are not real elements, or empty — usually a sign of a misread column
bond_source, n_bonds whether connectivity is explicit (from the file, e.g. SDF bonds or PDB CONECT) or inferred from geometry
altloc_atoms, low_occupancy_atoms alternate conformations / partial occupancies (PDB)
warnings mmCIF validity problems (needs the cif extra; degrades to a note without it)

report.clean is True when nothing in report.issues fired (no atoms lost, no junk element symbols, no CIF validity failure). Like the report above, it offers to_dict() (JSON) and report_markdown(), and the CLI mirrors the same flags:

molscope qc 3ptb.pdb                 # one-line inventory
molscope qc --fetch 1ubq --json      # full JSON report
molscope qc ligand.sdf --out qc.md   # write a Markdown report

Which one do I want?

Reach for molscope qc as a quick gate on any structure file — it is fast, needs only NumPy, and surfaces parse problems (lost atoms, bad element columns, missing metadata, surprise inference of bonds). Reach for molscope structure-report when the structure is a protein headed for distance- or graph-based ML and you need the topology verdict (backbone integrity, chain breaks, net charge).

One report for everything: molscope report

When you want the whole picture in a single shareable file rather than one verdict at a time, molscope report bundles the QC verdicts above with the chain / ligand inventory, a descriptor table, contact-map statistics and an embedded heatmap, molecular-graph stats, and an optional coarse-grained preview:

molscope report 3ptb.pdb --out-dir report/            # writes report/report.html
molscope report --fetch 1ubq --format both            # HTML and Markdown
molscope report 3ptb.pdb --coarse-grain --cg-mapping martini

The HTML is self-contained (figures are inline data: URIs), so it travels as a single file. The same data is available programmatically:

import molscope as ms

data = ms.build_report("3ptb.pdb", coarse_grain="residue_com")
ms.report.render_html(data)        # a self-contained HTML string
ms.report.render_markdown(data)    # a Markdown string

Sections whose inputs are missing — a residue contact map for a residue-less .xyz, say — are skipped with a note rather than failing.

Preflight guardrails

qc and structure-report answer "is this file any good?" up front. Preflight answers the narrower, just-in-time question "will this featurisation step quietly give me a worse result on this input?" — and it is built to run right before that step.

It reuses the same cheap signals (quality_report, and with deep=True prepare_structure) and turns the ones that matter into explicit, workflow-scoped warnings: bonds inferred from geometry rather than read from the file (so graph edges and CG bonds depend on a distance threshold), missing residue metadata (no residue-level features), no hydrogens, alternate locations / partial occupancy, unrecognised element symbols, and atom counts large enough that an atom-level dense distance matrix would allocate gigabytes.

molscope preflight 3ptb.pdb                       # all warnings, with topology checks
molscope preflight 1ubq.pdb --workflow graph      # only what affects to_graph()
molscope export "data/*.pdb" --to pyg -o graphs/ --preflight   # warn per structure first

Or opt in from Python, right where the work happens:

import molscope as ms

mol = ms.read("1ubq.pdb")
mol.preflight(workflow="graph").summary()   # inspect the PreflightReport
g = mol.to_graph(preflight=True)            # same graph, warnings emitted first
desc = mol.descriptors(preflight=True)      # likewise; values are unchanged

Preflight never changes a result — it only tells you when one is about to be quietly degraded.

Which QC tool do I want?

molscope qc for "did this file parse?", molscope structure-report for "is this protein ML-ready?", and molscope preflight for "what about this descriptor / graph / CG run on this input?" right before you launch it.