biasmv / pv

WebGL protein viewer
https://pv.readthedocs.org
MIT License
319 stars 86 forks source link

Support for displaying a MODEL/ENDMDL ensemble #102

Closed xiang-jun closed 9 years ago

xiang-jun commented 9 years ago

It is refreshing to see pv. Reading through the doc, I found that "In case multiple models are present in the file (as designated by MODEL/ENDMDL), only the first is read." Would it be possible for pv to read multiple structures and display one at a time? This functionality would be handy for visualizing a set of relevant structures in one MODEL/ENDMDL ensemble, as for NMR structures in the PDB.

Thanks,

Xiang-Jun

biasmv commented 9 years ago

I've added support for loading all structures from a multi-pdb file. By default, only the first model is read. To load all models, you must pass loadAllModels : true to pv.io.pdb. Example use:

  pv.io.fetchPdb('/pdbs/1nmr.pdb', function(structures) {
    viewer.clear()
    for (var i = 0; i < structures.length; ++i) {
      viewer.cartoon('ensemble_'+ i, structures[i]);
    }
    viewer.autoZoom();
  }, { loadAllModels : true } );
xiang-jun commented 9 years ago

Very neat! Could you also show an example where each model can be displayed one-by-one by keyboard/mouse click as in Jmol/JSmol?

xiang-jun commented 9 years ago

One more point: the pv MODEL/ENDMDL ensemble reader seems to assume just different conformations of the same structure as in PDB NMR structures. However, for my purpose, I use the MODEL/ENDMDL ensemble to collect a set of related but different structures. As a concrete example, the four triplets in the yeast phenylalanine tRNA (1ehz, see file '1ehz-triplets.pdb': http://x3dna.org/luxfiles/1ehz-triplets.pdb) cannot be displayed using your sample code. Any play to support this non-canonical use of MODEL/ENDMDL ?

biasmv commented 9 years ago

This code shows how to display only one structure at a time. By pressing 'n', 'p', the next/previous structure is shown.


var ENSEMBLE = null;

var activeIndex = 0;

function ensemble() {
  io.fetchPdb('/pdbs/1nmr.pdb', function(structures) {
    viewer.clear()
    var i = 0;
    ENSEMBLE = structures.map(function(a) {
      return viewer.cartoon('ensemble.' + i++, a);
    });
    updateVisibility();
    viewer.autoZoom();
  }, { loadAllModels : true } );
}
function updateVisibility() {
  ENSEMBLE.forEach(function(a) { a.hide(); });
  ENSEMBLE[activeIndex].show();
  viewer.requestRedraw();
}

$(document).keypress(function(ev) {
  if (String.fromCharCode(ev.charCode) === 'n') {
    activeIndex = (activeIndex + 1)  % ENSEMBLE.length;
    updateVisibility();
  }
  if (String.fromCharCode(ev.charCode) === 'p') {
    // make sure we never calculate modulo on negative value
    activeIndex = (activeIndex - 1 + ENSEMBLE.length)  % ENSEMBLE.length;
    updateVisibility();
  }
});
biasmv commented 9 years ago

Regarding your second question: I've downloaded your 1ehz-triplets.pdb triplet and used it with the sample code. The multi-model PDB loading code loads them just fine. Since the structures contain 3 separate nucleotides that are not directly connected, the cartoon render mode is not able to display anything. You will have to use one of the modes that display structures at full connectivity level. If you change the cartoon call to e.g. lines, the structures are displayed just fine.

For your use-case the multi-PDB loading code should work just fine. The only part which assumes that the structures are related are the secondary structure and BIOMT assignment code. As long as you do not have secondary structure definitions and REMARK 350 records in your PDB file, you are safe. In case that's a problem for your, I'll add an option to disable REMARK 30 and secondary structure records (HELIX, SHEET) loading.

xiang-jun commented 9 years ago

Thank you so much for your effort in making pv to support the display of an MODEL/ENDMDL ensemble. It is understandable why the cartoon mode does not display the dis-connected base triplets in my 1ehz-triplets.pdb file. All I need right now is that is can be displayed in e.g. lines mode, with nucleotides labeled. My field is in nucleic acid structures, so the default setting in pv may not directly apply.

I am not that familiar with JavaScript yet. Playing your updated code (with 'n'/'p') in the small sample application in pv doc page, I did not see any structure shown. Presumably I am missing something simple. Would it be possible that you create a complete example (using 1ehz-triplets.pdb) to show the new pv functionality for displaying an MODEL/ENDMDL ensemble? Ideally, the initially display shows only the first model, with 'n' and 'p' for the next and previous model, and 'e' for the whole ensemble as shown in your initial code example.

Thanks a lot.

biasmv commented 9 years ago

The following (complete) example does what you want. You will have to add bio-pv.min.js and 1ehz-triplet.pdf to the same folder containing the sample page below.

<html>
<head>
  <title>Sample</title>
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  <style>
    #viewer {
      width : 100%;
      height : 100%;
      overflow:hidden;
    }
  </style>
</head>
<body>
<div id=viewer></div>
</body>
<script type='text/javascript' src='bio-pv.min.js'></script>
<script type='text/javascript'>
// override the default options with something less restrictive.
var options = {
  width: 'auto',
  height: 'auto',
  antialias: true,
  quality : 'medium'
};
// insert the viewer under the Dom element with id 'viewer'.
var viewer = pv.Viewer(document.getElementById('viewer'), options);

var ENSEMBLE = null;
var activeIndex = 0;

viewer.on('viewerReady', function() {
  io.fetchPdb('1ehz-triplets.pdb', function(structures) {
    viewer.clear()
    var i = 0;
    ENSEMBLE = structures.map(function(a) {
      return viewer.lines('ensemble.' + i++, a);
    });
    updateVisibility();
    viewer.autoZoom();
  }, { loadAllModels : true } );
});

function updateVisibility() {
  ENSEMBLE.forEach(function(a) { a.hide(); });
  ENSEMBLE[activeIndex].show();
  viewer.requestRedraw();
}

document.addEventListener('keypress', function(ev) {
  if (String.fromCharCode(ev.charCode) === 'n') {
    activeIndex = (activeIndex + 1)  % ENSEMBLE.length;
    updateVisibility();
  }
  if (String.fromCharCode(ev.charCode) === 'p') {
    // make sure we never calculate modulo on negative value
    activeIndex = (activeIndex - 1 + ENSEMBLE.length)  % ENSEMBLE.length;
    updateVisibility();
  }
  if (String.fromCharCode(ev.charCode) === 'e') {
    ENSEMBLE.forEach(function(a) { a.show(); });
    viewer.requestRedraw();
  }
});
</script>
xiang-jun commented 9 years ago

That's brilliant -- Just tested and it works!

I completely echo your minimal, self-contained approach. I will certainly use pv in the future. It may also be possible that we do something together to bring more DSSR functionality for nucleic acid structures into pv. See sample illustrations. There is currently a DSSR/Jmol interface and a DSSR plugin for PyMOL is in progress.

Best regards,

biasmv commented 9 years ago

I'm glad the sample worked for you. That's one of the main advantages of a pure JS viewer. It's very easy to extend with custom functionality.

Thanks for the sample illustrations! Representation of DNA and RNA is certainly not PV's strong point. From my perspective having support for the block representation is certainly feasible and not too far off. The functionality for the block representation is all there and just need to be glued together. The trickiest part is to fit the blocks to the base atoms.

I've created ticket #106 to track progress for this feature. I'm not sure how much time I'll have to work on this in the coming months, so don't expect progress right away. Any help in that area is more than welcome.

Best, Marco