--- 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