Skip to content

Commit e20069e

Browse files
committed
Add a Show Electron Density menu item, listener and demo
Puts the feature in reach from the alignment viewer's View menu. The fetch runs on a SwingWorker: even the smallest source is a few hundred kilobytes and a full-resolution map can be far larger, so fetching on the event dispatch thread would freeze the window for the duration. Requests are built with allowNonRenderableFormats(false), which keeps the map coefficient source out of the chain automatically rather than relying on the viewer to notice it cannot draw the result. When nothing is available the dialog explains why rather than listing HTTP codes: for an entry whose every source returns 404 the likely reason is that no structure factors were deposited and there is no associated EMDB map, which is worth saying plainly. The per-source detail is still shown underneath. AbstractAlignmentJmol gains getFrame() and setStatus() so a listener in the neighbouring package can own its dialogs and report progress; both were previously reachable only as protected fields. DemoShowElectronDensity displays 1CBS with both maps clipped around the bound retinoic acid.
1 parent 4f58238 commit e20069e

4 files changed

Lines changed: 325 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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 org.biojava.nbio.structure.PdbId;
21+
import org.biojava.nbio.structure.Structure;
22+
import org.biojava.nbio.structure.StructureIO;
23+
import org.biojava.nbio.structure.align.gui.jmol.StructureAlignmentJmol;
24+
import org.biojava.nbio.structure.io.density.DensityMapCache;
25+
import org.biojava.nbio.structure.io.density.DensityMapKind;
26+
import org.biojava.nbio.structure.io.density.DensityMapResult;
27+
28+
/**
29+
* Shows 1CBS with its electron density drawn around the bound retinoic acid.
30+
* <p>
31+
* Both maps are displayed: the 2mFo-DFc map in blue at 1 sigma, which should hug
32+
* the ligand closely, and the mFo-DFc difference map as a red and green pair at
33+
* 3 sigma, which for a well refined structure should show very little.
34+
* <p>
35+
* Once the window is up, the map can be manipulated from the Rasmol command box
36+
* at the bottom, for instance
37+
* <pre>
38+
* isosurface ID "bj_density_2fofc" delete
39+
* </pre>
40+
* to remove just the blue surface. Pressing Reset Display keeps the maps, since
41+
* they are folded into the saved state when they are drawn.
42+
*
43+
* @author Amr ALHOSSARY
44+
* @since 7.3.0
45+
*/
46+
public class DemoShowElectronDensity {
47+
48+
/**
49+
* @param args an optional PDB ID to display instead of the default
50+
* @throws Exception if the structure or the map could not be fetched
51+
*/
52+
public static void main(String[] args) throws Exception {
53+
String id = args.length > 0 ? args[0] : "1cbs";
54+
55+
Structure structure = StructureIO.getStructure(id);
56+
StructureAlignmentJmol viewer = new StructureAlignmentJmol();
57+
viewer.setStructure(structure);
58+
viewer.evalString("select all; cartoon on; color chain; "
59+
+ "select ligand; wireframe 0.16; spacefill 0.4; color cpk;");
60+
61+
DensityMapCache cache = new DensityMapCache();
62+
System.out.println("Density cache: " + cache.getCachePath());
63+
64+
// Clipping to the ligand keeps the surface readable and, for a large map,
65+
// keeps the contouring quick enough not to stall the interface.
66+
for (DensityMapKind kind : new DensityMapKind[] {DensityMapKind.TWO_FO_FC, DensityMapKind.FO_FC}) {
67+
cache.findDensityMap(new PdbId(id), kind).ifPresent(map -> {
68+
System.out.printf("%-8s from %-20s %s (%,d bytes)%n",
69+
map.getKind(), map.getSource(), map.getFile().getName(), map.getFileSizeBytes());
70+
viewer.getJmolPanel().loadDensityMap(map, "{ligand}", 5.0);
71+
});
72+
}
73+
74+
DensityMapResult any = cache.findDensityMap(new PdbId(id), DensityMapKind.AUTO).orElse(null);
75+
if (any == null) {
76+
System.out.println("No density is available for " + id
77+
+ " - try an entry with deposited structure factors, such as 1cbs.");
78+
}
79+
}
80+
}

biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/MenuCreator.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ public class MenuCreator {
6666
public static final String PAIRWISE_ALIGN = "New Pairwise Alignment";
6767
public static final String MULTIPLE_ALIGN = "New Multiple Alignment";
6868
public static final String PHYLOGENETIC_TREE = "Phylogenetic Tree";
69+
/** @since 7.3.0 */
70+
public static final String SHOW_DENSITY = "Show Electron Density";
6971
protected static final int keyMask =
7072
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
7173

@@ -169,6 +171,8 @@ public static JMenuBar initJmolMenu(JFrame frame,
169171
distMax.setMnemonic(KeyEvent.VK_D);
170172
distMax.addActionListener(new MyDistMaxListener(parent));
171173
view.add(distMax);
174+
//Electron density
175+
view.add(getShowDensityMenuItem(parent));
172176
//Dot Plot - only if the alignment was an afpChain
173177
if (afpChain != null){
174178
JMenuItem dotplot = new JMenuItem(DOT_PLOT);
@@ -229,6 +233,22 @@ public static JMenuItem getOpenPDBMenuItem() {
229233
return openI;
230234
}
231235

236+
/**
237+
* Menu item that fetches and displays the electron density or cryo-EM map for
238+
* the structure currently on screen.
239+
*
240+
* @param parent the viewer to draw the map into
241+
* @return the menu item
242+
* @author Amr ALHOSSARY
243+
* @since 7.3.0
244+
*/
245+
public static JMenuItem getShowDensityMenuItem(AbstractAlignmentJmol parent) {
246+
JMenuItem densityI = new JMenuItem(SHOW_DENSITY);
247+
densityI.setMnemonic(KeyEvent.VK_E);
248+
densityI.addActionListener(new MyShowDensityListener(parent));
249+
return densityI;
250+
}
251+
232252

233253
public static JMenuItem getLoadMenuItem() {
234254

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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 org.biojava.nbio.structure.align.gui;
19+
20+
import java.awt.event.ActionEvent;
21+
import java.awt.event.ActionListener;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.concurrent.ExecutionException;
26+
27+
import javax.swing.JOptionPane;
28+
import javax.swing.SwingWorker;
29+
30+
import org.biojava.nbio.structure.PdbId;
31+
import org.biojava.nbio.structure.Structure;
32+
import org.biojava.nbio.structure.align.gui.jmol.AbstractAlignmentJmol;
33+
import org.biojava.nbio.structure.io.density.DensityMapCache;
34+
import org.biojava.nbio.structure.io.density.DensityMapKind;
35+
import org.biojava.nbio.structure.io.density.DensityMapRequest;
36+
import org.biojava.nbio.structure.io.density.DensityMapResult;
37+
import org.biojava.nbio.structure.io.density.DensityMapSource;
38+
import org.biojava.nbio.structure.io.density.NoDensityMapException;
39+
import org.slf4j.Logger;
40+
import org.slf4j.LoggerFactory;
41+
42+
/**
43+
* Fetches the density map for the structure on screen and draws it.
44+
* <p>
45+
* The download happens on a {@link SwingWorker} rather than the event dispatch
46+
* thread. Even the smallest source runs to a few hundred kilobytes and a
47+
* full-resolution map can be far larger, so fetching inline would freeze the
48+
* interface for as long as it took.
49+
*
50+
* @author Amr ALHOSSARY
51+
* @since 7.3.0
52+
*/
53+
public class MyShowDensityListener implements ActionListener {
54+
55+
private static final Logger logger = LoggerFactory.getLogger(MyShowDensityListener.class);
56+
57+
private static final String OPTION_2FOFC = "2Fo-Fc (electron density)";
58+
private static final String OPTION_FOFC = "Fo-Fc (difference)";
59+
private static final String OPTION_BOTH = "Both";
60+
private static final String OPTION_AUTO = "Whatever is available";
61+
62+
private final AbstractAlignmentJmol parent;
63+
64+
/**
65+
* @param parent the viewer to draw into
66+
*/
67+
public MyShowDensityListener(AbstractAlignmentJmol parent) {
68+
this.parent = parent;
69+
}
70+
71+
@Override
72+
public void actionPerformed(ActionEvent e) {
73+
Structure structure = parent == null ? null : parent.getStructure();
74+
if (structure == null) {
75+
JOptionPane.showMessageDialog(parent == null ? null : parent.getFrame(),
76+
"There is no structure on screen to fetch a density map for.",
77+
"Show Electron Density", JOptionPane.INFORMATION_MESSAGE);
78+
return;
79+
}
80+
81+
PdbId pdbId = structure.getPdbId();
82+
if (pdbId == null) {
83+
String typed = JOptionPane.showInputDialog(parent.getFrame(),
84+
"This structure has no PDB ID. Enter one to look up its density map:",
85+
"Show Electron Density", JOptionPane.QUESTION_MESSAGE);
86+
if (typed == null || typed.trim().isEmpty()) {
87+
return;
88+
}
89+
try {
90+
pdbId = new PdbId(typed.trim());
91+
} catch (IllegalArgumentException ex) {
92+
JOptionPane.showMessageDialog(parent.getFrame(), typed + " is not a valid PDB ID.",
93+
"Show Electron Density", JOptionPane.ERROR_MESSAGE);
94+
return;
95+
}
96+
}
97+
98+
Object[] options = {OPTION_2FOFC, OPTION_FOFC, OPTION_BOTH, OPTION_AUTO};
99+
Object choice = JOptionPane.showInputDialog(parent.getFrame(),
100+
"Which map would you like to see for " + pdbId.getId() + "?",
101+
"Show Electron Density", JOptionPane.QUESTION_MESSAGE, null, options, OPTION_2FOFC);
102+
if (choice == null) {
103+
return;
104+
}
105+
106+
List<DensityMapKind> kinds = new ArrayList<>(2);
107+
if (OPTION_BOTH.equals(choice)) {
108+
kinds.add(DensityMapKind.TWO_FO_FC);
109+
kinds.add(DensityMapKind.FO_FC);
110+
} else if (OPTION_FOFC.equals(choice)) {
111+
kinds.add(DensityMapKind.FO_FC);
112+
} else if (OPTION_AUTO.equals(choice)) {
113+
kinds.add(DensityMapKind.AUTO);
114+
} else {
115+
kinds.add(DensityMapKind.TWO_FO_FC);
116+
}
117+
118+
fetchAndShow(pdbId, kinds);
119+
}
120+
121+
private void fetchAndShow(PdbId pdbId, List<DensityMapKind> kinds) {
122+
parent.setStatus("Fetching density map for " + pdbId.getId() + " ...");
123+
124+
new SwingWorker<List<DensityMapResult>, Void>() {
125+
126+
private NoDensityMapException missing;
127+
128+
@Override
129+
protected List<DensityMapResult> doInBackground() throws Exception {
130+
DensityMapCache cache = DensityMapCache.getInstance();
131+
List<DensityMapResult> results = new ArrayList<>(kinds.size());
132+
for (DensityMapKind kind : kinds) {
133+
try {
134+
// Restricting to displayable formats also excludes the map
135+
// coefficient source automatically.
136+
results.add(cache.getDensityMap(DensityMapRequest.builder(pdbId)
137+
.kind(kind)
138+
.allowNonRenderableFormats(false)
139+
.build()));
140+
} catch (NoDensityMapException ex) {
141+
missing = ex;
142+
}
143+
}
144+
return results;
145+
}
146+
147+
@Override
148+
protected void done() {
149+
List<DensityMapResult> results;
150+
try {
151+
results = get();
152+
} catch (InterruptedException ex) {
153+
Thread.currentThread().interrupt();
154+
return;
155+
} catch (ExecutionException ex) {
156+
logger.error("Could not fetch a density map for {}", pdbId.getId(), ex.getCause());
157+
parent.setStatus("Could not fetch density map");
158+
JOptionPane.showMessageDialog(parent.getFrame(),
159+
"Could not fetch the density map:\n" + ex.getCause().getMessage(),
160+
"Show Electron Density", JOptionPane.ERROR_MESSAGE);
161+
return;
162+
}
163+
164+
for (DensityMapResult result : results) {
165+
parent.getJmolPanel().loadDensityMap(result);
166+
}
167+
168+
if (results.isEmpty()) {
169+
parent.setStatus("No density map available");
170+
JOptionPane.showMessageDialog(parent.getFrame(), explain(pdbId, missing),
171+
"Show Electron Density", JOptionPane.INFORMATION_MESSAGE);
172+
} else {
173+
DensityMapResult first = results.get(0);
174+
parent.setStatus(String.format("Density from %s (%,d kB)",
175+
first.getSource(), first.getFileSizeBytes() / 1024));
176+
}
177+
}
178+
}.execute();
179+
}
180+
181+
/**
182+
* Turns the per-source reasons into something a user can act on, rather than a
183+
* list of HTTP codes.
184+
*/
185+
private static String explain(PdbId pdbId, NoDensityMapException missing) {
186+
StringBuilder sb = new StringBuilder("No density map is available for ")
187+
.append(pdbId.getId()).append(".\n\n");
188+
if (missing == null) {
189+
return sb.toString();
190+
}
191+
Map<DensityMapSource, String> attempts = missing.getAttempts();
192+
boolean allNotFound = !attempts.isEmpty() && attempts.values().stream()
193+
.allMatch(reason -> reason.startsWith("HTTP 404") || reason.startsWith("no "));
194+
if (allNotFound) {
195+
sb.append("The most likely reason is that no structure factors were deposited\n")
196+
.append("for this entry, and that it has no associated EMDB map.\n\n");
197+
}
198+
sb.append("Sources tried:\n");
199+
attempts.forEach((source, reason) -> sb.append(" ").append(source).append(": ").append(reason).append('\n'));
200+
return sb.toString();
201+
}
202+
}

biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/jmol/AbstractAlignmentJmol.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,29 @@ public Structure getStructure(){
194194
*/
195195
public abstract List<Matrix> getDistanceMatrices();
196196

197+
/**
198+
* The window this viewer lives in, for use as the owner of dialogs raised from
199+
* outside this package.
200+
*
201+
* @return the frame, which may be <code>null</code> before the window is built
202+
* @since 7.3.0
203+
*/
204+
public JFrame getFrame() {
205+
return frame;
206+
}
207+
208+
/**
209+
* Writes a short message into the viewer's status field.
210+
*
211+
* @param message the message; ignored if there is no status field yet
212+
* @since 7.3.0
213+
*/
214+
public void setStatus(String message) {
215+
if (status != null) {
216+
status.setText(message);
217+
}
218+
}
219+
197220
/**
198221
* Set the title of the AlignmentJmol window.
199222
* @param title

0 commit comments

Comments
 (0)