How to handle Unmodelled Density Blobs
Scanned 9/1/2026
Install to Claude Code
npx -y skills add pemsley/coot --skill blobs --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Blobs?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/pemsley-blobs)More formats (shields.io, HTML) on the badges page.
---
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:
```python
# Find blobs in difference map (molecule 2) at 3 sigma
blobs = coot.find_blobs_py(0, 2, 3.0)
# Returns: [[[x, y, z], score], ...]
# Higher score = larger/stronger blob
# Go to the biggest blob
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:
```python
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)
# Check all atoms near the blob
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:**
```python
# Don't do this for chain extension!
coot.add_OXT_to_residue(0, "A", 93, "") # Wrong!
```
**Correct approach:**
```python
# Extend the chain by adding residues
checkpoint = coot.make_backup_checkpoint(0, "before chain extension")
# Add residues one at a time, refining as you go
coot.add_terminal_residue(0, "A", 93, "auto", 1) # Adds residue 94
coot.refine_residues_py(0, [["A", 93, ""], ["A", 94, ""]])
coot.accept_moving_atoms_py()
# Check if blob is still there
blobs = coot.find_blobs_py(0, 2, 3.0)
# If blob persists (maybe smaller), add another residue
coot.add_terminal_residue(0, "A", 94, "auto", 1) # Adds residue 95
coot.refine_residues_py(0, [["A", 94, ""], ["A", 95, ""]])
coot.accept_moving_atoms_py()
# Continue until blob is gone or no more density
```
### Iterative Chain Extension Workflow
```python
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):
# Check for remaining blob near current terminus
blobs = coot.find_blobs_py(imol, imol_map, 3.0)
if not blobs:
break
# Get position of current C-terminus
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
# Check if any blob is near the C-terminus
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: # Within 6 Angstroms
blob_near_terminus = True
break
if not blob_near_terminus:
break
# Add next residue
result = coot.add_terminal_residue(imol, chain_id, current_resno, "auto", 1)
if result != 1:
break
current_resno += 1
residues_added += 1
# Refine the new region
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()
# Navigate to see progress
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
# Usage:
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:
```python
# After extending chain A as far as the density allows
# Check there's no more blob near the terminus
blobs = coot.find_blobs_py(0, 2, 3.0)
# If no blob near C-terminus, cap the chain:
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
1. **Go to the blob** - `set_rotation_centre()`
2. **Find closest atoms** - not just closest residue CAs
3. **Check if near chain terminus** - look for C, O atoms of last residue
4. **If near terminus**: extend chain with `add_terminal_residue()`, don't add OXT
5. **If near side chain**: might be missing atoms, use `fill_partial_residue()`
6. **If isolated**: might be water, ion, or ligand
7. **Always checkpoint first** - `make_backup_checkpoint()`
8. **Refine after changes** - check if blob score decreases
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!