Skip to content

Commit f6aefce

Browse files
committed
Add electron density and cryo-EM map fetching with a fallback chain
Adds org.biojava.nbio.structure.io.density, which downloads and caches density maps the same way LocalPDBDirectory caches coordinate files, and hands back a File that a viewer can contour. Closes #947. Several services publish density for the PDB and they differ enormously in size for the same entry, so rather than picking one, sources are tried in order until one answers. The order is smallest-adequate-first, because the smallest form is usually perfectly good to look at: X-ray: RCSB density server -> PDBe CCP4 -> PDBe density server -> wwPDB map coefficients (disabled by default) cryo-EM: RCSB density server -> PDBe density server -> EMDB primary map For 1cbs a density server slice is about a tenth the size of the equivalent pair of CCP4 files. For the map behind 6hu9 it is 3.7 MB against a 106 MB primary map. A size limit, 256 MiB by default, is checked against the size EMDB itself reports before any of the body is transferred, and exceeding it is not an error: the chain simply falls back to a smaller representation. wwPDB map coefficients are supported for completeness, since they are the route RCSB documents now that edmaps.rcsb.org has shut down, but they are structure factors rather than a sampled grid and cannot be displayed without a Fourier transform. They are therefore disabled by default, and DensityFileFormat carries an isJmolLoadable() flag so that a viewer can refuse them rather than silently drawing nothing. Notes on the design: * A source that has nothing for an entry is skipped and the next is tried, but any other transport failure aborts the chain. A network outage must never be reported as "this entry has no density". When every source is exhausted, NoDensityMapException carries the reason from each one, so a caller can say why rather than just that it failed. * A density server response contains both the 2Fo-Fc and the Fo-Fc blocks, so the two kinds share one cache entry instead of downloading the identical file twice. Which block to read is a display-time decision. * Cryo-EM entries are found through their EMDB identifier, looked up from EMDB's search API with RCSB as a fallback. That lookup also yields the contour level the depositors recommend, which is how an EM map should be contoured; it is attached to the result whichever source supplied the voxels. The experimental method is never inferred from resolution, which BioJava parses incorrectly for some cryo-EM entries (#1000). * Ccp4Header checks for the MAP stamp at byte 208, so a server that answers with an error page and HTTP 200 produces a clean cache miss rather than a corrupt cache entry. * Cached results are fully described by a .meta sidecar, so LOCAL_ONLY requests are served without opening a connection. DemoFetchElectronDensity exercises all three outcomes: an X-ray entry, a cryo-EM entry resolved through EMDB, and an entry deposited without structure factors.
1 parent 2a57e19 commit f6aefce

20 files changed

Lines changed: 3616 additions & 0 deletions
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* BioJava development code
3+
*
4+
* This code may be freely distributed and modified under the terms of the GNU
5+
* Lesser General Public Licence. This should be distributed with the code. If
6+
* you do not have a copy, see:
7+
*
8+
* http://www.gnu.org/copyleft/lesser.html
9+
*
10+
* Copyright for this code is held jointly by the individual authors. These
11+
* should be listed in @author doc comments.
12+
*
13+
* For more information on the BioJava project and its aims, or to join the
14+
* biojava-l mailing list, visit the home page at:
15+
*
16+
* http://www.biojava.org/
17+
*/
18+
package demo;
19+
20+
import java.io.IOException;
21+
22+
import org.biojava.nbio.structure.PdbId;
23+
import org.biojava.nbio.structure.io.density.DensityMapCache;
24+
import org.biojava.nbio.structure.io.density.DensityMapKind;
25+
import org.biojava.nbio.structure.io.density.DensityMapResult;
26+
import org.biojava.nbio.structure.io.density.NoDensityMapException;
27+
28+
/**
29+
* Fetches electron density and cryo-EM maps, printing which source answered.
30+
* <p>
31+
* The three entries chosen exercise the three outcomes the fallback chain has to
32+
* handle:
33+
* <ul>
34+
* <li><b>1cbs</b> &mdash; an X-ray structure with deposited structure factors,
35+
* served by the first source tried.</li>
36+
* <li><b>6hu9</b> &mdash; a cryo-EM structure. Every X-ray source has nothing for
37+
* it, so the chain resolves the associated EMDB entry instead and picks up the
38+
* author-recommended contour level along the way.</li>
39+
* <li><b>4hhb</b> &mdash; deposited in 1984 without structure factors, so no
40+
* source has anything. This is a normal outcome, not an error, and the exception
41+
* says which sources were tried and why each declined.</li>
42+
* </ul>
43+
*
44+
* @author Amr ALHOSSARY
45+
* @since 7.3.0
46+
*/
47+
public class DemoFetchElectronDensity {
48+
49+
/**
50+
* @param args ignored
51+
* @throws IOException if a server could not be reached at all
52+
*/
53+
public static void main(String[] args) throws IOException {
54+
DensityMapCache cache = new DensityMapCache();
55+
System.out.println("Caching under: " + cache.getCachePath());
56+
System.out.println();
57+
58+
show(cache, "1cbs", DensityMapKind.TWO_FO_FC);
59+
show(cache, "1cbs", DensityMapKind.FO_FC);
60+
show(cache, "6hu9", DensityMapKind.AUTO);
61+
show(cache, "4hhb", DensityMapKind.AUTO);
62+
}
63+
64+
private static void show(DensityMapCache cache, String id, DensityMapKind kind) throws IOException {
65+
System.out.printf("%s (%s)%n", id, kind);
66+
try {
67+
DensityMapResult result = cache.getDensityMap(new PdbId(id), kind);
68+
System.out.printf(" source : %s%n", result.getSource());
69+
System.out.printf(" format : %s%s%n", result.getFormat(),
70+
result.isRenderable() ? "" : " (needs an FFT before display)");
71+
System.out.printf(" kind : %s%n", result.getKind());
72+
System.out.printf(" file : %s (%,d bytes)%n", result.getFile(), result.getFileSizeBytes());
73+
System.out.printf(" cached : %s%n", result.isFromCache());
74+
if (result.getEmdbId() != null) {
75+
System.out.printf(" EMDB : %s%n", result.getEmdbId());
76+
}
77+
if (result.getRecommendedContourLevel() != null) {
78+
System.out.printf(" contour : %s (author recommended)%n", result.getRecommendedContourLevel());
79+
}
80+
if (result.getContourInSigma() != null) {
81+
System.out.printf(" in sigma : %.2f%n", result.getContourInSigma());
82+
}
83+
} catch (NoDensityMapException e) {
84+
System.out.printf(" no map available%n");
85+
e.getAttempts().forEach((source, reason) ->
86+
System.out.printf(" %-24s %s%n", source, reason));
87+
}
88+
System.out.println();
89+
}
90+
}

0 commit comments

Comments
 (0)