3dmol / 3Dmol.js

WebGL accelerated JavaScript molecular graphics library
Other
771 stars 190 forks source link

visualize the 3d crystal in cif through py3Dmol #461

Closed qzhu2017 closed 3 years ago

qzhu2017 commented 3 years ago

I am working on the code which I want to visualize the 3D molecular crystal packing in Jupyter notebook. Through web search, I found py3Dmol is very convenient for the visualization of 3D molecules. However, I don't know how to display the crystals. Current I can stack the molecules in the same panel as follows,

image

I have two questions here.

  1. how can I assign different color scheme to these two molecules? (say, one is darker and the other is brighter)
  2. can I add some lines to draw the box?
  3. can I directly pass the CIF file to py3Dmol and let it display the 3D structure?

I read through some old issues and it seems that people already realized the function to parse cif and display the 3D structure. However, I failed to find the example to use that function.

dkoes commented 3 years ago

addUnitCell will draw the unit cell of the current model, if that information is available in the molecular data. CIF is a supported input format, although in setting up a test case for you I found and fixed some bugs with the parser (can now deal with missing atom_site_type_symbol). By default, the different symmetries cannot be styled differently since they are rendered by applying the symmetry translations to the source object. However, you can specify that the atoms should be explicitly duplicated with the duplicateAssemblyAtoms options in which case they can be selected and styled. However, there wasn't a way to select atoms from different symmetries, so I've added the sym property to support this.

Here is example py3Dmol code:

import py3Dmol
cif = open('9002806.cif').read()
viewer = py3Dmol.view()
viewer.addModel(cif,'cif',{'doAssembly':True,'duplicateAssemblyAtoms':True})
viewer.setStyle({'sphere':{'colorscheme':'Jmol','scale':.5},'stick':{'colorscheme':'Jmol'}})
viewer.addUnitCell()

viewer.setStyle({'sym':2},{'sphere':{'scale':.5,'color':'blue'},'stick':{'color':'cyan'}})

viewer.zoomTo()

And a JavaScript example: http://3dmol.csb.pitt.edu/tests/auto/generate_test.cgi?test=testSymmStyle

Let me know how it works for you.

qzhu2017 commented 3 years ago

@dkoes Thanks for your reply. I just tried your script. It can read the cif. There is only a minor issue that the molecule is not centered in the unit cell. Maybe it needs a small fix? For your convenience, I am attaching you the cif file which I used for test.

254385.cif.zip

import py3Dmol
cif = open('254385.cif').read()
viewer = py3Dmol.view()
viewer.addModel(cif,'cif',{'doAssembly':True,'duplicateAssemblyAtoms':True})
viewer.setStyle({'sphere':{'colorscheme':'Jmol','scale':.5},'stick':{'colorscheme':'Jmol'}})
viewer.addUnitCell()
#viewer.setStyle({'sym':2},{'sphere':{'scale':.5,'color':'blue'},'stick':{'color':'cyan'}})
viewer.zoomTo()
image
qzhu2017 commented 3 years ago

I just briefly checked the code 3Dmol/glviewer.js. It seems that it also has the functions like addLine and addBox. Can you describe how to use these functions? I tried to add the following to the script,

view.addLine({start:{x:0,y:0,z:0},end:{x:5,y:0,z:0}})

but it complains

NameError: name 'start' is not defined
qzhu2017 commented 3 years ago

Never mind, I think I know what happened. I have to organize the arguments as a Python dictionary. The following script works.

view.addArrow({"start": {"x":-10.0, "y":0.0, "z":0.0}, "end": {"x":0.0, "y":-10.0, "z":0.0}})
dkoes commented 3 years ago

As far as I can tell, 254385.cif is being displayed correctly given the contents of the file. We have probably run into the limits of my understanding of CIF files and x-ray crystallography. The transformations specified in the file place the molecule outside the unit cell (fractional coordinates are larger than 1.0 or negative). If you can explain what in the CIF file is suppose to prevent this from happening, I can try to fix it.

qzhu2017 commented 3 years ago

@dkoes I see. Let's talk about how you parse the cif file. Suppose you have a coordinate (0, 0.8, 0.1) and symmetry operation -x, -y, -z, you will get (-0, -0.8, -0.1). However, for a better visualization, placing all the atoms within the range of [0, 1] is more convenient. So I will use (0, 0.2, 0.9), instead of (0, -0.8, -0.1). In the periodic system, symmetry operation [-x, -y, -z] is equivalent to [1-x, 1-y, 1-z] or [-x, 1-y, -z].

I usually do the following

import numpy as np
coordinates -= np.floor(coordinates)

I suggest you add an option of rescale to allow arranging all atoms inside the unit cell box. This will be good if you have atomic crystals such as diamond.

Btw, I have two small questions as a crystallographer,

A good example can be found in the following link. Choose JAPWIH, and then choose unitcell or 3*3*3 in the dropdown menu of packing

image

For visualizing molecular crystal (e.g, 254385.cif), the risk is that you may break the molecule into several parts. When people create the cif file, they perhaps intentionally place some atoms outside the [0, 1] bound. So the key is to make sure that the symmetry operation needs to optimized.

I just checked 254385.cif, it has four symmetry operations.

 55 'x, y, z'
 56 '-x+1/2, -y, z+1/2'
 57 'x+1/2, -y, z+1/2'
 58 '-x, y, z'

If I modify them to:

55 'x, y, z'
 56 '-x+1/2, 2-y, z+1/2'
 57 'x+1/2, 2-y, z+1/2'
 58 '-x, y, z'

with the same py3Dmol script, I got the following,

image

Probably, this is not a problem of py3Dmol, people needs to pass the right symmetry operations to the cif before they visualize the structure.

dkoes commented 3 years ago

I looked through the Jmol code and it has a normalize feature that translates molecules in unit cell increments to force their center to be in the unit cell. I've implemented this as the normalizeAssembly option. I've also made it the default with CIF to detect bonds between atoms (even from different symmetries) if duplicateAssemblyAtoms is true (this isn't an option otherwise as when explicit atoms aren't created the renderer just stamps out the exact same 3D object with different transforms - this is much much faster when looking at large assemblies like viral capsids). The bond detection behavior can be disabled with dontConnectDuplicatedAtoms.

I've also added the ability to style the unit cell box, including styling axis arrows and labels, and made the default include all these features.

You can see everything in action here: http://3dmol.csb.pitt.edu/tests/auto/generate_test.cgi?test=testCIFsurf

Any suggestions for improvements?

Would a replicateUnitCell(A,B,C) viewer method that replicates a model's unit cell A/B/C times in each direction do what you want? Should bonds be detected between the replicated cells?

qzhu2017 commented 3 years ago

Thanks. the example looks good. I also tried it on my examples. They look great! Perhaps, you can also provide some instructions regarding how to adjust the style of the UnitCell box. Sometimes, the arrows are too thick when the crystal unit cell is small.

Btw, how to use the replicateUnitCell(A,B,C) method. I tried to apply this method, but the structure does not show up.

import py3Dmol
cif = open('254385.cif').read()
viewer = py3Dmol.view()
viewer.addModel(cif,'cif',{'doAssembly':True,'duplicateAssemblyAtoms':True,'normalizeAssembly':True})
viewer.setStyle({'sphere':{'colorscheme':'Jmol','scale':.2},'stick':{'colorscheme':'Jmol', 'radius': 0.1}})
viewer.addUnitCell()
viewer.replicateUnitCell(2,2,2)
#viewer.setStyle({'sym':2},{'sphere':{'scale':.5,'color':'blue'},'stick':{'color':'cyan'}})
viewer.zoomTo()

Is my script correct?

dkoes commented 3 years ago

replicateUnitCell didn't work because I hadn't implemented it yet - I was asking for feedback on the api first. In any event, I've implemented it and it should work now: http://3dmol.csb.pitt.edu/tests/auto/generate_test.cgi?test=testreplicateUnitCell