Testing for Python - Batch Render Orbitals

@arugula_jones - I have very crude script here:

(I say crude … the code to connect and send messages to the desktop app needs to be polished and put into a Python class, so from avogadro import connection is possible.)

The main part of the script is:

for fileName in glob.glob("*.fchk"):
        # open the file
        print("Opening " + fileName)
        path = cwd + "/" + fileName
        basename = path[:-5] # strip off the .fchk
        sendMessage("openFile", {"fileName": path})

        sendMessage("renderOrbital", options)
        # wait 1 minute to compute the surface
        # .. adjust as needed
        time.sleep(60)
        print("Saving HOMO")
        sendMessage("saveGraphic", { "fileName": basename + "-homo.png" })

The surfaces dialog now accepts a few messages:

  • renderOrbital or renderMO
  • renderElectronDensity
  • renderVDW
  • renderSolventAccessible

The one catch is that generating surfaces is asynchronous, so the current solution is just to have the Python code wait 60 seconds before sending the saveGraphic message.

But it’ll go through a pile of .fchk or other files, open them, save orbitals, etc. Of course you can generate multiple orbitals and save the graphic of each, or the electron density, etc.

Suggestions? Concerns? General thoughts?

Oh, right. At the moment, the script requires this pull request:

I don’t know when the ‘nightly’ snapshots are handled. If anyone wants to test it, please let me know what OS / binary would work best?

I understand

" […] so the current solution is just to have the Python code wait 60 seconds before sending the saveGraphic message. […]"

as 1) a command is assembled, 2) Python broadcasts this command to an other part of Avogadro, 3) Python assumes 60 seconds are probably enough for the task to be completed, to 4) continue with other work (e.g., saveGraphic).

If so, Python’s module subprocess (part of the standard library) has an optional flag check=True. Could this be used to collect the exit signal of task renderOrbital as “task has been completed” which might happen earlier, or later than the fix threshold of a minute? I apologize for not providing a MWE, this comment only is conceptual.

No, the “problem” is that at the moment, Avogadro generates surfaces and meshes entirely asynchronously on its own threads.

So I need to do some more “plumbing” to have Avogadro send back a JSON signal when the work is done. At that point, the script can just wait for the signal.

Until then, we have this. (I’m guessing it’s close enough for some purposes.)

Thanks folks, I‘ll try to use this and see how it goes.

I think that the script interface works well. It looks like it shortcuts that problem of the surfaces dialogue presenting duplicate options! Nevermind, but I think it is choosing the correct option. I am curious as to whether you have ‘hooks’ to specify color map or smoothing? (For lower quality renders, smoothing can have a significant effect, so I like to specify explicitly)

Once the alignment tools are script-ready this is going to be an absolute machine!!

I fixed a few small bugs in the surface dialog, but it still needs work.

Yeah, it’s on the list. (I was also adding some hooks to enable / disable display types today).

Yep. As you can see in the Feedback on Crystal Previews - #5 by ghutchis thread, I ripped through ~500 crystal files, including use of Python image manipulation to crop and rescale the screenshots.

Oops. Think I found a bug. The orbitals are off by one? Ie HOMO renders LUMO, LUMO renders LUMO+1, and so forth?
Actually after LUMO so far as I can tell - it just keeps repeating LUMO+1.

Also, was this a typo?
if (options.contains(“isovalue”) &&
options[“isolvalue”].canConvert()) {
bool ok;
probably there is a more appropriate way to raise this issue but I am new to git.

So for the off-by-one error, you’re requesting the HOMO and it’s giving you the LUMO, HOMO-1 gives you HOMO, etc.?

Is that when you use text or an orbital number?

Like you can have:

{
   "orbital": "homo-1"
}

or

{
   "orbital": 5
}

I guess I’m also curious about the LUMO+1 issue. I seemed to be able to get higher orbitals, but it didn’t get a lot of testing.

Yes, correct. Here is the code, using the text.

orbitals_to_render = ["homo", "homo-1", "homo-2", "lumo", "lumo+1", "lumo+2"]

cwd = os.getcwd()
for fileName in glob.glob("*.fchk"):
    # open the file
    print("Opening " + fileName)
    path = cwd + "/" + fileName
    basename = path[:-5]  # strip off the .fchk
    sendMessage("openFile", {"fileName": path})

    for orbital in orbitals_to_render:
        options = {
            "orbital": orbital,
            "isovalue": 0.003,
            "resolution": 0.10
        }

        sendMessage("renderOrbital", options)
        # wait 1 minute to compute the surface
        # .. adjust as needed
        time.sleep(5)

        print(f"Saving {orbital.upper()}")
        sendMessage("saveGraphic", {"fileName": f"{basename}-{orbital}.png"})
        print("saved")
        time.sleep(1)

After a cursory look through your code, maybe it has something to do with how you are converting the text to numbers, as you do so differently for HOMO and LUMO?

edit: yes, looks like a typo. Added comments on the commit.

I should mention that I also tried to use “orbital”: int to no avail.

Right now, it’s been “index”: int … but using an integer for “orbital” is a much better idea.