Author Topic: Rendering Images with Python Script  (Read 78 times)

bjcowen

  • Newbie
  • *
  • Posts: 4
Rendering Images with Python Script
« on: May 10, 2018, 09:01:20 PM »
Hello:

I have a question regarding just using the the ovitos console to produce rendered images. Note that everything I am trying to do I can accomplish by logging into the GUI. Since I am dealing with a 27 million atom system, the python scripting offers a much more convenient solution. In my code below, I am just trying to render a "TopView" of my simulation. I defined it as ortho and put in the appropriate position and angle. Some things I do not understand though:

1) Why is the first rendered image more zoomed in than the rest?
2) Why do I not see a scale bar on my image when I (thought) I had implemented it correctly in my code.


Any help would be greatly appreciated!

Code: [Select]
import ovito
from ovito.io import *
from ovito.vis import *
from ovito import dataset
from math import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

node = import_file("../../sputter_position_v3.xyz", multiple_frames = True)

vp = ovito.dataset.viewports.active_vp

vp.type=Viewport.Type.ORTHO
vp.camera_pos=(251.497,251.551,550.188)
vp.camera_dir=(0,0,-1)
cell = node.source.cell
cell.display.enabled = False
node.add_to_scene()

for frame in range(0,dataset.anim.last_frame+1):
dataset.anim.current_frame=frame
vp = ovito.dataset.viewports.active_vp
rs = RenderSettings(size=(1920,1440), filename="t"+str(frame)+".png",renderer=TachyonRenderer())


bar_length = 100 # Simulation units (e.g. Angstroms)
bar_color = QColor(0,0,0)
label_text = "{} nm".format(bar_length/10)
label_color = QColor(255,255,255)


def render_overlay(painter, **args):
if args['is_perspective']:
raise Exception("This only works with non-perspective viewports.")
# Compute length of bar in screen space
screen_length = 0.5 * bar_length * painter.window().height() / args['fov']
# Define geometry of bar in screen space
height = 0.04 * painter.window().height()
margin = 0.02 * painter.window().height()
rect = QRectF(108, 31, screen_length, height)
# Render bar
painter.fillRect(rect, bar_color)
# Render text label
font = painter.font()
font.setPixelSize(height)
painter.setFont(font)
painter.setPen(QPen(label_color))
painter.drawText(rect, Qt.AlignCenter, label_text)
overlay = PythonViewportOverlay(function=render_overlay)
vp.overlays.append(overlay)
anim = vp.render(rs)

Alexander Stukowski

  • Administrator
  • Sr. Member
  • *****
  • Posts: 369
Re: Rendering Images with Python Script
« Reply #1 on: May 11, 2018, 09:25:02 PM »
Dear Ben,

1) I am not really sure how this issue arises. What I noticed in your script is that you never set the field of view of the camera explicitly (i.e. the Viewport.fov parameter). You only set the camera position and the direction. My suggestion is that you either set the FOV value, i.e. the size of the visible render region, explicitly, or call the Viewport.zoom_all() to let OVITO choose the FOV value automatically in order to fully show the entire dataset.

2) Another mistake I noticed is that you add the user-defined viewport overlay function inside the for-loop that renders the series of frames. That means you keep adding multiple overlays to the viewport's list of overlays, one more with each frame. This certainly isn't what you want. You should rather add an overlay just once before entering the for-loop, i.e. in the following order:
Code: [Select]
# Install the user-defined viewport overlay, which will render the scale bar:
def render_overlay(painter, **args):
    ...
overlay = PythonViewportOverlay(function=render_overlay)
vp.overlays.append(overlay)

# Now render the frames, one by one:
for frame in range(0,dataset.anim.last_frame+1):
    ...
I'm not sure though why the scale bar is not showing. If you didn't modify the original code of the overlay function, which was taken from the OVITO docs, it should work. Perhaps it is related to the first issue above then. Let's see.

-Alex

bjcowen

  • Newbie
  • *
  • Posts: 4
Re: Rendering Images with Python Script
« Reply #2 on: May 12, 2018, 06:25:58 AM »
I have changed the code as you suggested but am having similar problems. Adding a value for the fov parameter allows me to zoom out to show the whole box, but the 1st frame is different than the rest in terms of its field of view. I have attached 2 images. The first t0.png is the first frame generated, and t1 is the second frame. Note that t2...n all have the same fov as each other, but t0.png is different, as shown in the image. All images will zoom out if I raise the fov number, but first some reason, the 1st frame is scaled differently.


Also, I have not touched any code related to the scale bar or anything like that, but the scale bar is not showing up. Is the scale bar not being added to the active viewport or something? I should also note this is ovito/2.9.0 if that helps diagnose anything. If you take my code, does it render a scale bar for you? Are your rendered 1st images the same scale as all others?

Also, when I try to go a different route with the zoom all feature, I get this error:

Code: [Select]
TypeError: zoom_all(): incompatible function arguments. The following argument types are supported:
    1. (self: ovito.plugins.PyScript.Viewport.Viewport) -> None

Code is below:

Code: [Select]
import ovito
from ovito.io import *
from ovito.vis import *
from ovito import dataset
from math import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

node = import_file("../../sputter_position_v3.xyz", multiple_frames = True)

vp = ovito.dataset.viewports.active_vp

vp.type=Viewport.Type.ORTHO
vp.camera_pos=(251.497,251.551,550.188)
vp.camera_dir=(0,0,-1)
vp.fov=400
cell = node.source.cell
cell.display.enabled = False
node.add_to_scene()

bar_length = 100 # Simulation units (e.g. Angstroms)
bar_color = QColor(0,0,0)
label_text = "{} nm".format(bar_length/10)
label_color = QColor(255,255,255)

# Install the user-defined viewport overlay, which will render the scale bar:
def render_overlay(painter, **args):
if args['is_perspective']:
raise Exception("This only works with non-perspective viewports.")
# Compute length of bar in screen space
screen_length = 0.5 * bar_length * painter.window().height() / args['fov']
# Define geometry of bar in screen space
height = 0.04 * painter.window().height()
margin = 0.02 * painter.window().height()
rect = QRectF(5, 5, screen_length, height)
# Render bar
painter.fillRect(rect, bar_color)
# Render text label
font = painter.font()
font.setPixelSize(height)
painter.setFont(font)
painter.setPen(QPen(label_color))
painter.drawText(rect, Qt.AlignCenter, label_text)   
overlay = PythonViewportOverlay(function=render_overlay)
vp.overlays.append(overlay)

# Now render the frames, one by one:
for frame in range(0,dataset.anim.last_frame+1):
dataset.anim.current_frame=frame
vp = ovito.dataset.viewports.active_vp
rs = RenderSettings(size=(1920,1440), filename="t"+str(frame)+".png",renderer=TachyonRenderer())
anim = vp.render(rs)
« Last Edit: May 12, 2018, 07:08:32 AM by bjcowen »

Alexander Stukowski

  • Administrator
  • Sr. Member
  • *****
  • Posts: 369
Re: Rendering Images with Python Script
« Reply #3 on: May 12, 2018, 02:07:30 PM »
I now ran your script myself with OVITO 2.9.0, and I can confirm that are indeed some issues. I was able to find a workaround for the zooming issue, but the viewport overlay function, which is needed to render the scale bar, seems to be broken in that program version. It might be that it cannot be used from a batch script at all, only within the GUI.

In this situation I suggest you switch to the current development build of OVITO 3.0.0 instead. The old 2.9 release is already one year old and issues like the one encountered here have been fixed since then. The Python programming interface has been further simplified, but that also means you will need to adjust the code accordingly. I have done that for you already. Below you find the updated script code, which should work correctly with OVITO 3.0.0-dev198 according to my tests:

Code: [Select]
from ovito.io import *
from ovito.vis import *
from math import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

pipeline = import_file("../../sputter_position_v3.xyz")
pipeline.add_to_scene()

# Turn off cell display:
pipeline.get_vis(SimulationCellVis).enabled = False

bar_length = 100 # Simulation units (e.g. Angstroms)
bar_color = QColor(0,0,0)
label_text = "{} nm".format(bar_length/10)
label_color = QColor(255,255,255)

# Define the overlay function, which renders the scale bar:
def render_overlay(args):
    # Compute length of bar in screen space
    screen_length = args.project_size((0,0,0), bar_length)

    # Define geometry of bar in screen space
    height = 0.07 * args.painter.window().height()
    margin = 0.02 * args.painter.window().height()
    rect = QRectF(margin, margin, screen_length, height)

    # Render bar
    args.painter.fillRect(rect, bar_color)

    # Render text label
    font = args.painter.font()
    font.setPixelSize(height)
    args.painter.setFont(font)
    args.painter.setPen(QPen(label_color))
    args.painter.drawText(rect, Qt.AlignCenter, label_text)

# Create a viewport and install overlay function:
vp = Viewport()
vp.overlays.append(PythonViewportOverlay(function=render_overlay))
# This selects a parallel projection with viewing direction along -Z axis:
vp.type = Viewport.Type.TOP
# Adjust camera pos and FOV automatically:
vp.zoom_all()
# Alternatively, set parameters explicitly:
#vp.camera_pos = (251.497,251.551,550.188)
#vp.fov=100

# Set up renderer
renderer = TachyonRenderer()

# Now render the frames, one by one:
for frame in range(pipeline.source.num_frames):
    print("Rendering frame", frame)
    vp.render_image(frame=frame, size=(1920,1440), filename="t"+str(frame)+".png", renderer=renderer)

bjcowen

  • Newbie
  • *
  • Posts: 4
Re: Rendering Images with Python Script
« Reply #4 on: May 15, 2018, 10:16:12 PM »
Dr. Stukowski,

Upgrading to the newest version of OVITO seems to have fixed all the issues. I was wondering if you could help me with one more thing.
I'd like to use the python scripting to only output interstitials and vacancies, which I have done below.

Code: [Select]
from ovito.io import *
from ovito.vis import *
from ovito.modifiers import *
from math import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

pipeline = import_file("../../../../sputter_position.xyz")
pipeline.add_to_scene()

# Turn off cell display:
pipeline.get_vis(SimulationCellVis).enabled = False

bar_length = 100 # Simulation units (e.g. Angstroms)
bar_color = QColor(0,0,0)
label_text = "{} nm".format(bar_length/10)
label_color = QColor(255,255,255)

# Define the overlay function, which renders the scale bar:
def render_overlay(args):
    # Compute length of bar in screen space
    screen_length = args.project_size((0,0,0), bar_length)

    # Define geometry of bar in screen space
    height = 0.05 * args.painter.window().height()
    margin = 0.02 * args.painter.window().height()
    rect = QRectF(308,1300, screen_length, height)

    # Render bar
    args.painter.fillRect(rect, bar_color)

    # Render text label
    font = args.painter.font()
    font.setPixelSize(height)
    args.painter.setFont(font)
    args.painter.setPen(QPen(label_color))
    args.painter.drawText(rect, Qt.AlignCenter, label_text)


# Perform Wigner-Seitz analysis:
ws = WignerSeitzAnalysisModifier(
    per_type_occupancies = False,
    eliminate_cell_deformation = True)
ws.reference.load("../Equilibrate/position.dump")
pipeline.modifiers.append(ws)

pipeline.modifiers.append(SelectExpressionModifier(expression = 'Occupancy==1'))
pipeline.modifiers.append(DeleteSelectedParticlesModifier())

# Create a viewport and install overlay function:
vp = Viewport()
vp.overlays.append(PythonViewportOverlay(function=render_overlay))
# This selects a parallel projection with viewing direction along -Z axis:
vp.type = Viewport.Type.TOP
# Adjust camera pos and FOV automatically:
vp.zoom_all()
# Alternatively, set parameters explicitly:
#vp.camera_pos = (251.497,251.551,550.188)
#vp.fov=100

# Set up renderer
renderer = TachyonRenderer()

# Now render the frames, one by one:
for frame in range(pipeline.source.num_frames):
    print("Rendering frame", frame)
    vp.render_image(frame=frame, size=(1920,1440), filename="t"+str(frame)+".png", renderer=renderer)


Note that I selected per type occupancies to no. I was wondering, however, if you could show me how to color vacancies corresponding to atom type 1, say, light blue, and vacancies corresponding to atom type 2, dark red, or something like that. Then I would want to do the same with interstitials. Thus the end product would be all vacancies are colored specific colors depending on the site type, and then all interstitials are colored a specific color depending on the interstitial type.

I know you have a doc page: https://ovito.org/manual/python/modules/ovito_modifiers.html#ovito.modifiers.WignerSeitzAnalysisModifier

But I am having trouble getting this to work.

Could you show me how to do this? I will continue trying different things to see if I can get it to work.
Note that I imagine the interstitials would correspond to interstitial sites, not the interstitial atoms themselves. Since we are dealing with WS, we are just looking at sites, not the actual atoms.
« Last Edit: May 15, 2018, 10:18:57 PM by bjcowen »

Alexander Stukowski

  • Administrator
  • Sr. Member
  • *****
  • Posts: 369
Re: Rendering Images with Python Script
« Reply #5 on: May 18, 2018, 12:15:06 PM »
Giving vacancy sites a certain color depending on the type of atom that originally occupied that site in the perfect crystal is straightforward. Simply use a ExpressionSelectionModifier followed by an AssignColorModifier:

Code: [Select]
pipeline.modifiers.append(ExpressionSelectionModifier(expression = 'Occupancy==1 && ParticleType==1'))
pipeline.modifiers.append(AssignColorModifier(color=(0.3, 0.3, 1.0))
pipeline.modifiers.append(ExpressionSelectionModifier(expression = 'Occupancy==1 && ParticleType==2'))
pipeline.modifiers.append(AssignColorModifier(color=(0.2, 0.6, 1.0))
...

(Note that this code is for OVITO 3.0-dev. The SelectExpressionModifier class from OVITO 2.9 has been renamed to ExpressionSelectionModifier.)

Coloring interstitial atoms depending on their chemical type is less straightforward, but can probably done and will probably require turning on the per_type_occupancies option. I guess you want interstitials to be coloured according to their own chemical type, not the chemical type of the site they are sitting on, right? If yes, then one has find a way to determine which of the two (or more) atoms occupying a site is the interstitial and which one is the original atom (if any).

Alexander Stukowski

  • Administrator
  • Sr. Member
  • *****
  • Posts: 369
Re: Rendering Images with Python Script
« Reply #6 on: May 18, 2018, 05:22:07 PM »
Can you please post the entire render.py file as an attachment. From the error message I cannot tell where this is coming from.

bjcowen

  • Newbie
  • *
  • Posts: 4
Re: Rendering Images with Python Script
« Reply #7 on: May 18, 2018, 05:29:06 PM »
Sorry, I deleted my message, was going to post my full code: First, error message:
Code: [Select]
ERROR: The Python script 'render.py' has exited with an error.
  File "render.py", line 53
    pipeline.modifiers.append(ExpressionSelectionModifier(expression = 'Occupancy==1 && ParticleType==2'))
           ^
SyntaxError: invalid syntax


Also I am assuming you meant Occupancy==0 in the code for vacancies

And here is my code:

Code: [Select]
from ovito.io import *
from ovito.vis import *
from ovito.modifiers import *
from math import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

pipeline = import_file("../../../../sputter_position.xyz")
pipeline.add_to_scene()

# Turn off cell display:
pipeline.get_vis(SimulationCellVis).enabled = False

bar_length = 100 # Simulation units (e.g. Angstroms)
bar_color = QColor(0,0,0)
label_text = "{} nm".format(bar_length/10)
label_color = QColor(255,255,255)

# Define the overlay function, which renders the scale bar:
def render_overlay(args):
    # Compute length of bar in screen space
    screen_length = args.project_size((0,0,0), bar_length)

    # Define geometry of bar in screen space
    height = 0.05 * args.painter.window().height()
    margin = 0.02 * args.painter.window().height()
    rect = QRectF(308,1300, screen_length, height)

    # Render bar
    args.painter.fillRect(rect, bar_color)

    # Render text label
    font = args.painter.font()
    font.setPixelSize(height)
    args.painter.setFont(font)
    args.painter.setPen(QPen(label_color))
    args.painter.drawText(rect, Qt.AlignCenter, label_text)


# Perform Wigner-Seitz analysis:
ws = WignerSeitzAnalysisModifier(
    per_type_occupancies = False,
    eliminate_cell_deformation = True)
ws.reference.load("../Equilibrate/position.dump")
pipeline.modifiers.append(ws)

pipeline.modifiers.append(ExpressionSelectionModifier(expression = 'Occupancy==1'))
pipeline.modifiers.append(DeleteSelectedParticlesModifier())


pipeline.modifiers.append(ExpressionSelectionModifier(expression = 'Occupancy==0 && ParticleType==1'))
pipeline.modifiers.append(AssignColorModifier(color=(0.3, 0.3, 1.0))
pipeline.modifiers.append(ExpressionSelectionModifier(expression = 'Occupancy==0 && ParticleType==2'))
pipeline.modifiers.append(AssignColorModifier(color=(0.2, 0.6, 1.0))


# Create a viewport and install overlay function:
vp = Viewport()
vp.overlays.append(PythonViewportOverlay(function=render_overlay))
# This selects a parallel projection with viewing direction along -Z axis:
vp.type = Viewport.Type.TOP
# Adjust camera pos and FOV automatically:
vp.zoom_all()
# Alternatively, set parameters explicitly:
#vp.camera_pos = (251.497,251.551,550.188)
#vp.fov=100

# Set up renderer
renderer = TachyonRenderer()

# Now render the frames, one by one:
for frame in range(pipeline.source.num_frames):
    print("Rendering frame", frame)
    vp.render_image(frame=frame, size=(1920,1440), filename="t"+str(frame)+".png", renderer=renderer)
« Last Edit: May 18, 2018, 05:54:03 PM by bjcowen »

Alexander Stukowski

  • Administrator
  • Sr. Member
  • *****
  • Posts: 369
Re: Rendering Images with Python Script
« Reply #8 on: May 18, 2018, 10:12:43 PM »
Yeah, my mistake, here in this line:
Code: [Select]
pipeline.modifiers.append(AssignColorModifier(color=(0.3, 0.3, 1.0))

The third closing ')' is missing at the end to match the three opening '('.