Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/scientific-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ jobs:
Rscript tests/ensemble.R
Rscript tests/batch.R
shell: bash
- name: Verify AlphaFold and ESMFold confidence formats
run: Rscript tests/predictions.R
- name: Verify AlphaFold, ESMFold and experimental counterpart formats
run: |
Rscript tests/predictions.R
Rscript tests/counterparts.R
shell: bash
- name: Verify residue review, sequence, pairwise alignment and model selection
run: Rscript tests/inspection.R
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ RamplotR is an open-source R Shiny application that brings backbone geometry, a
| --- | --- |
| **Explore a structure** | Interactive φ/ψ plots with several reference-density datasets, native RamplotR density regions and a parallel six-class Rama8000 standard validation that matches the current cctbx/Phenix categories. |
| **Inspect residues in context** | Synchronized Ramachandran plot, searchable residue table, all-chain sequence navigator and NGL 3D viewer. The issue queue distinguishes Rama8000 outliers from native RamplotR `Not allowed` regions and missing angles. |
| **Work with predicted models** | Import AlphaFold DB models or upload AlphaFold 2/3, ColabFold and ESMFold structures. Examine pLDDT/PAE and analyse multiple AF2/ColabFold or ESMFold seeds as a prediction ensemble with residue-level φ/ψ, Rama8000 and confidence agreement. |
| **Work with predicted models** | Import AlphaFold DB models or upload AlphaFold 2/3, ColabFold and ESMFold structures. Examine pLDDT/PAE, analyse prediction ensembles, and discover experimentally determined PDBe counterparts for direct residue-level comparison. |
| **Examine structural geometry** | Explore peptide ω, side-chain χ1 and descriptive Cβ measurements. Optionally attach the matching deposited structure's official wwPDB validation report for independent rotamer, clash and geometry annotations. |
| **Inspect experimental evidence** | Overlay a local CCP4/MRC cryo-EM map in the 3D viewer as a qualitative aid, without uploading the map to a separate service. |
| **Compare models** | Sequence-align chains from two structures; use the Conformational Change Explorer to navigate residue-level wrapped φ/ψ displacement, then inspect paired residues in linked 2D/3D views alongside native and Rama8000 category changes. |
Expand Down
68 changes: 68 additions & 0 deletions docs/experimental-counterparts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Experimental counterparts for predicted structures

RamplotR can discover experimentally determined PDB structures for a predicted
protein and load a selected structure directly into the pairwise comparison
workflow.

## Data source

Counterpart discovery uses the 3D-Beacons Hub API with a UniProt accession.
RamplotR only presents records whose provider is **PDBe** and whose model
category is explicitly **EXPERIMENTALLY DETERMINED**.

This distinction is deliberate: template-based models, AlphaFold models,
conformational ensembles and records from other providers are not relabelled as
experimental evidence.

## Workflow

For an AlphaFold DB model, RamplotR already knows the UniProt accession and
performs the lookup automatically after the model is loaded.

For an uploaded AlphaFold 2 / ColabFold, ESMFold or other predicted structure,
open the prediction-confidence panel, enter the matching UniProt accession and
choose **Find experimental structures**.

Returned structures are ranked by:

1. mapped UniProt sequence coverage, highest first;
2. experimental resolution, lowest first when available.

Resolution can legitimately be unavailable for some experimental methods, such
as NMR; these structures remain in the result set.

Select one hit and choose **Compare selected structure**. RamplotR loads the PDB
entry into the existing Compare tab, where chain selection, sequence alignment,
wrapped delta-phi/delta-psi, the Conformational Change Explorer and linked
paired 3D inspection remain available.

## Interpretation

A structure mapped to the same UniProt accession is a useful experimental
counterpart, but it is not automatically an equivalent biological state.
Deposited structures may differ in:

- construct boundaries;
- mutations or engineered residues;
- ligand or cofactor state;
- oligomeric state;
- experimental conditions;
- unresolved segments;
- post-translational modifications;
- conformational state.

RamplotR therefore uses the lookup as a discovery/navigation step. Scientific
interpretation should still inspect sequence alignment, coverage, structure
metadata and the local residue context.

## Privacy and networking

Local uploaded coordinates are **not** sent to 3D-Beacons. The lookup sends only
the UniProt accession supplied by the user. Choosing a counterpart then loads
that public PDB entry through RamplotR's existing PDB input workflow.

## Reproducibility

The parsing and filtering logic is covered by `tests/counterparts.R` using a
mock 3D-Beacons response. Browser workflows do not depend on the live service,
so external downtime does not make RamplotR's core CI flaky.
16 changes: 16 additions & 0 deletions docs/prediction-confidence.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ Confidence is attached to a specific prediction model, not automatically
transferred to unrelated models in an NMR ensemble. For additional models
choose the model and supply compatible confidence data separately.

## Experimental counterpart discovery

For a loaded prediction, the confidence panel also offers an **Experimental
counterparts** workflow. AlphaFold DB inputs retain their UniProt accession and
query 3D-Beacons automatically. For uploaded prediction models, provide the
matching UniProt accession manually.

Only experimentally determined **PDBe** records are shown. Selecting one loads
that PDB entry directly into RamplotR's Compare tab for sequence-aligned
residue-level phi/psi and 3D inspection. A shared UniProt accession is useful
evidence that the structures represent the same protein, but it does not imply
the same construct, ligand state, oligomeric state or conformation.

See [Experimental counterparts for predicted structures](experimental-counterparts.md)
for details and limitations.

## Interpretation and provenance

pLDDT is a prediction of local correctness, while PAE measures expected
Expand Down
126 changes: 126 additions & 0 deletions shinyRam/R/counterparts.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Experimental counterpart discovery for predicted structures.
#
# 3D-Beacons provides a UniProt-centred index of experimental and predicted
# structure models. RamplotR only treats PDBe records explicitly labelled
# EXPERIMENTALLY DETERMINED as experimental counterparts.

ram_uniprot_accession <- function(accession) {
value <- toupper(trimws(as.character(accession)))
if(length(value)!=1L || is.na(value) ||
!grepl("^[A-Z0-9]{6,12}(?:-[0-9]+)?$",value))
stop("Enter a valid UniProt accession.")
value
}

ram_3dbeacons_summary_url <- function(accession) {
accession <- ram_uniprot_accession(accession)
paste0(
"https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/",
utils::URLencode(accession,reserved=TRUE),".json"
)
}

ram_3dbeacons_fetch <- function(url) {
if(!requireNamespace("jsonlite",quietly=TRUE))
stop("Install jsonlite to query experimental counterparts.")
jsonlite::fromJSON(url,simplifyVector=FALSE)
}

ram_counterpart_scalar <- function(value, default=NA_character_) {
if(is.null(value)) return(default)
flat <- unlist(value,use.names=FALSE)
if(!length(flat) || is.na(flat[[1L]])) return(default)
as.character(flat[[1L]])
}

ram_counterpart_numeric <- function(value) {
out <- suppressWarnings(as.numeric(ram_counterpart_scalar(value,NA_character_)))
if(length(out)!=1L || !is.finite(out)) NA_real_ else out
}

ram_counterpart_pdb_id <- function(identifier) {
value <- toupper(ram_counterpart_scalar(identifier,""))
hit <- regexpr("^[0-9][A-Z0-9]{3}",value,perl=TRUE)
if(hit[[1L]]<0L) return(NA_character_)
regmatches(value,hit)
}

ram_counterpart_chains <- function(entities) {
if(is.null(entities) || !is.list(entities)) return("")
chains <- unique(unlist(lapply(entities,function(entity) {
type <- toupper(ram_counterpart_scalar(entity$entity_type,""))
if(!identical(type,"POLYMER")) return(character())
as.character(unlist(entity$chain_ids,use.names=FALSE))
}),use.names=FALSE))
chains <- chains[!is.na(chains) & nzchar(chains)]
paste(chains,collapse=", ")
}

ram_parse_experimental_counterparts <- function(response, accession,
max_results=50L) {
accession <- ram_uniprot_accession(accession)
if(!is.list(response)) stop("Invalid 3D-Beacons response.")
structures <- response$structures
if(is.null(structures) || !length(structures))
return(data.frame(
accession=character(),pdb_id=character(),model_identifier=character(),
chains=character(),coverage=numeric(),resolution=numeric(),
uniprot_start=integer(),uniprot_end=integer(),
experimental_method=character(),model_url=character(),
stringsAsFactors=FALSE
))
rows <- lapply(structures,function(item) {
summary <- if(is.list(item)) item$summary else NULL
if(is.null(summary) || !is.list(summary)) return(NULL)
provider <- toupper(ram_counterpart_scalar(summary$provider,""))
category <- toupper(gsub("[-_]"," ",
ram_counterpart_scalar(summary$model_category,"")))
if(provider!="PDBE" ||
!category %in% c("EXPERIMENTALLY DETERMINED","EXPERIMENTAL"))
return(NULL)
identifier <- ram_counterpart_scalar(summary$model_identifier,"")
pdb_id <- ram_counterpart_pdb_id(identifier)
if(is.na(pdb_id)) return(NULL)
data.frame(
accession=accession,
pdb_id=pdb_id,
model_identifier=identifier,
chains=ram_counterpart_chains(summary$entities),
coverage=ram_counterpart_numeric(summary$coverage),
resolution=ram_counterpart_numeric(summary$resolution),
uniprot_start=suppressWarnings(as.integer(
ram_counterpart_numeric(summary$uniprot_start))),
uniprot_end=suppressWarnings(as.integer(
ram_counterpart_numeric(summary$uniprot_end))),
experimental_method=ram_counterpart_scalar(
summary$experimental_method,""),
model_url=ram_counterpart_scalar(summary$model_url,""),
stringsAsFactors=FALSE
)
})
rows <- rows[!vapply(rows,is.null,logical(1))]
if(!length(rows)) return(ram_parse_experimental_counterparts(
list(structures=list()),accession,max_results))
result <- do.call(rbind,rows)

# One UniProt mapping may expose multiple records for the same PDB/chain.
# Keep the best-covered / highest-resolution representative of duplicates.
coverage_rank <- replace(result$coverage,!is.finite(result$coverage),-Inf)
resolution_rank <- replace(result$resolution,!is.finite(result$resolution),Inf)
result <- result[order(-coverage_rank,resolution_rank,result$pdb_id,
result$model_identifier),,drop=FALSE]
key <- paste(result$pdb_id,result$chains,sep="\r")
result <- result[!duplicated(key),,drop=FALSE]

max_results <- suppressWarnings(as.integer(max_results))
if(length(max_results)!=1L || is.na(max_results) || max_results<1L)
stop("max_results must be a positive integer.")
utils::head(result,max_results)
}

ram_lookup_experimental_counterparts <- function(
accession,max_results=50L,fetch=ram_3dbeacons_fetch) {
accession <- ram_uniprot_accession(accession)
response <- fetch(ram_3dbeacons_summary_url(accession))
ram_parse_experimental_counterparts(response,accession,max_results)
}
Loading
Loading