| name | coot-unmodelled-blobs |
| description | How to handle Unmodelled Density Blobs |
Handling Unmodeled Density Blobs
Investigating Blobs
When find_blobs_py() identifies unmodeled density, investigate what's actually there before acting:
blobs = coot.find_blobs_py(0, 2, 3.0)
if blobs:
biggest = blobs[0]
pos, score = biggest[0], biggest[1]
coot.set_rotation_centre(pos[0], pos[1], pos[2])
Determining What a Blob Represents
Don't just look at which residues are "nearby" by CA distance - this can be misleading for non-spherical blobs.
Instead, find which atoms are closest to the blob centre:
blob_x, blob_y, blob_z = 59.92, 3.06, -4.23
import math
def dist(x1, y1, z1, x2, y2, z2):
return math.sqrt((x2-x1)**2 + (y2-y1)**2 + (z2-z1)**2)
nearby_atoms = []
for chain in ['A', 'B']:
for resno in range(1, 150):
atoms = coot.residue_info_py(0, chain, resno, "")
if atoms:
res_name = coot.residue_name_py(0, chain, resno, "")
for atom in atoms:
x, y, z = atom[2]
d = dist(blob_x, blob_y, blob_z, x, y, z)
if d < 5.0:
atom_name = atom[0][0].strip()
nearby_atoms.append((d, chain, resno, res_name, atom_name))
nearby_atoms.sort()
for d, chain, resno, res_name, atom_name in nearby_atoms[:10]:
print(f"{d:.1f}Å: {chain}/{resno} {res_name} {atom_name}")
Chain Extension vs Terminal Atoms
Critical distinction:
If a blob is near the C atom and O atom of the last residue in a chain, it usually means:
- ❌ NOT a missing OXT (terminal carboxyl oxygen)
- ✅ More residues to build - the chain continues!
During model building, incomplete chains are common. The density beyond the last modeled residue represents unbuilt residues, not terminal atoms.
Wrong approach:
coot.add_OXT_to_residue(0, "A", 93, "")
Correct approach:
checkpoint = coot.make_backup_checkpoint(0, "before chain extension")
coot.add_terminal_residue(0, "A", 93, "auto", 1)
coot.refine_residues_py(0, [["A", 93, ""], ["A", 94, ""]])
coot.accept_moving_atoms_py()
blobs = coot.find_blobs_py(0, 2, 3.0)
coot.add_terminal_residue(0, "A", 94, "auto", 1)
coot.refine_residues_py(0, [["A", 94, ""], ["A", 95, ""]])
coot.accept_moving_atoms_py()
Iterative Chain Extension Workflow
def extend_chain_into_density(imol, chain_id, last_resno, imol_map, max_residues=10):
"""Extend a chain into unmodeled density."""
checkpoint = coot.make_backup_checkpoint(imol, f"before extending {chain_id}")
current_resno = last_resno
residues_added = 0
for i in range(max_residues):
blobs = coot.find_blobs_py(imol, imol_map, 3.0)
if not blobs:
break
atoms = coot.residue_info_py(imol, chain_id, current_resno, "")
c_pos = None
for atom in atoms:
if atom[0][0].strip() == "C":
c_pos = atom[2]
break
if not c_pos:
break
blob_near_terminus = False
for blob in blobs:
pos = blob[0]
d = ((pos[0]-c_pos[0])**2 + (pos[1]-c_pos[1])**2 + (pos[2]-c_pos[2])**2)**0.5
if d < 6.0:
blob_near_terminus = True
break
if not blob_near_terminus:
break
result = coot.add_terminal_residue(imol, chain_id, current_resno, "auto", 1)
if result != 1:
break
current_resno += 1
residues_added += 1
specs = [[chain_id, r, ""] for r in range(current_resno - 2, current_resno + 1)
if r > 0]
coot.refine_residues_py(imol, specs)
coot.accept_moving_atoms_py()
coot.set_go_to_atom_chain_residue_atom_name(chain_id, current_resno, "CA")
print(f"Added {residues_added} residues to chain {chain_id}")
return residues_added
extend_chain_into_density(0, "A", 93, 2)
When to Add OXT
Add OXT when you've finished building a fragment and there's no more density to extend into:
blobs = coot.find_blobs_py(0, 2, 3.0)
coot.add_OXT_to_residue(0, "A", 96, "")
coot.refine_residues_py(0, [["A", 95, ""], ["A", 96, ""]])
coot.accept_moving_atoms_py()
The key distinction:
- Blob near terminus → extend chain with
add_terminal_residue()
- No blob, chain fully built → cap with
add_OXT_to_residue()
Summary: Blob Investigation Checklist
- Go to the blob -
set_rotation_centre()
- Find closest atoms - not just closest residue CAs
- Check if near chain terminus - look for C, O atoms of last residue
- If near terminus: extend chain with
add_terminal_residue(), don't add OXT
- If near side chain: might be missing atoms, use
fill_partial_residue()
- If isolated: might be water, ion, or ligand
- Always checkpoint first -
make_backup_checkpoint()
- Refine after changes - check if blob score decreases