Author Topic: Change zoom (not camera position) in python script  (Read 56 times)

benlindsay

  • Newbie
  • *
  • Posts: 5
Change zoom (not camera position) in python script
« on: May 12, 2019, 10:18:19 PM »
I have an ovitos script to generate images from a trajectory, but I'm having trouble getting the results I want. My script looks like this:

Code: [Select]
from ovito import dataset
from ovito.io import import_file
from ovito.vis import TextLabelOverlay
from ovito.vis import Viewport
from ovito.vis import RenderSettings
from ovito.vis import OpenGLRenderer
from PyQt5 import QtCore
import numpy as np
import sys
from pathlib import Path

input_file = Path(sys.argv[1])
output_file = input_file.parent / (input_file.stem + ".png")

node = import_file(str(input_file), multiple_frames=True)
node.add_to_scene()
vp = Viewport(type=Viewport.Type.PERSPECTIVE)
node.compute()
overlay = TextLabelOverlay(
    text="[SourceFrame]",
    alignment=QtCore.Qt.AlignRight ^ QtCore.Qt.AlignBottom,
    # offset_y = 0.1,
    font_size=0.05,
    text_color=(0, 0, 0),
)
vp.overlays.append(overlay)

# Set camera position and direction
distance_factor = 1
cell = node.source.cell
x0 = cell.matrix[:, 3]
xlat = cell.matrix[:, 0]
ylat = cell.matrix[:, 1]
zlat = cell.matrix[:, 2]
boxorigin = x0 + (cell.matrix[:, :3] * 0.5).sum(axis=1)
theta = 20 * np.pi / 180
rorbit = np.linalg.norm(zlat) * distance_factor
pxyz = np.array([rorbit * np.cos(theta), rorbit * np.sin(theta), boxorigin[2]])
dxyz = -(pxyz - boxorigin)
vp.camera_pos = pxyz
vp.camera_dir = dxyz

rs = RenderSettings(
    filename=str(output_file),
    size=(800, 300),
    range=RenderSettings.Range.ANIMATION,
    renderer=OpenGLRenderer(),
)
vp.render(rs)

As is, the script has distance_factor set to 1. With that setting, the long simulation box fills the image size well, as shown in img1.png. However, the perspective is weird, because to fill the image, the camera has to be too close to the box. If I pull the camera farther away by setting distance_factor to 3 instead, then the perspective looks nicer, i.e. the lines pointing into the screen are a little more parallel. However, with this setup, the simulation box doesn't fill the screen, and cropping would cut out the frame number overlaid on the bottom right. Is there get the box filling of the first image, combined with the nice perspective of the 2nd image? Conceptually, this seems like changing the zooming in with the camera rather than physically moving the camera closer to the box.

As a side note, in case any of the math in the script seems weird, it may help to know that the z-axis is the long dimension of the box. I set my preferences so that the y-axis points up to get this shot. If there's a way to programmatically change that setting or enable this shot with the z-axis pointing up, I would also like to know how to do that. But manually changing that setting before doing this task isn't too bad.

Alexander Stukowski

  • Administrator
  • Hero Member
  • *****
  • Posts: 595
Re: Change zoom (not camera position) in python script
« Reply #1 on: May 13, 2019, 11:05:51 AM »
Hi,

You probably should set the Viewport.fov parameter to a smaller value. This should give you a more parallel type of perspective projection.

-Alex

Alexander Stukowski

  • Administrator
  • Hero Member
  • *****
  • Posts: 595
Re: Change zoom (not camera position) in python script
« Reply #2 on: May 13, 2019, 11:11:21 AM »
Regarding your second question: No, at the moment there is no way to select which coordinate axis points upward from Python. I will add a corresponding option in a future version of OVITO.

benlindsay

  • Newbie
  • *
  • Posts: 5
Re: Change zoom (not camera position) in python script
« Reply #3 on: May 13, 2019, 10:41:59 PM »
Hi,

You probably should set the Viewport.fov parameter to a smaller value. This should give you a more parallel type of perspective projection.

-Alex

Hi Alex,

Thanks for your quick response and all the work you've put into Ovito! I'm trying to use fov and weird things are happening that I can't make sense of. Starting with a distance_factor of 3, where the perspective looked good, but the simulation box was too small, I tried reducing fov so that the simulation box would show up larger in the image. However, most values that I tried produced no changes, but once I got small enough (on the order of fov=0.0005) then sometimes the simulation box would suddenly be zoomed in so the whole image was pure red. I tried narrowing down to a range of fov that bounds where this transition happens. I was getting into a range of around 0.0005 to 0.0006, but infuriatingly, sometimes the same fov would produce a super-zoomed in version and sometimes it would produce the same thing I had before.

I switched to a distance_factor of 1 and found similarly perplexing results. There's clearly something I'm missing about fov. If you could help me understand what I'm missing I would appreciate it.

And FYI, when playing around with this, the only 2 lines from my script above that I touched were replacing

Code: [Select]
vp = Viewport(type=Viewport.Type.PERSPECTIVE)
with

Code: [Select]
vp = Viewport(type=Viewport.Type.PERSPECTIVE, fov=###)
and changing

Code: [Select]
distance_factor = 1
to

Code: [Select]
distance_factor = 3
when necessary.

Alexander Stukowski

  • Administrator
  • Hero Member
  • *****
  • Posts: 595
Re: Change zoom (not camera position) in python script
« Reply #4 on: May 14, 2019, 09:52:34 AM »
Yes, I just realized that some more extra care is needed in this case. Sorry, for leaving you along with the problem too soon.

First, to find out what the default value of the FOV parameter is, you need to create a Viewport, set its projection type to 'perspective', and then query the current value of the FOV parameter:

Code: [Select]
>>> from ovito.vis import *                                                                                                                                                                             
>>> vp = Viewport()                                                                                                                                                                                     
>>> vp.type = Viewport.Type.PERSPECTIVE                                                                                                                                                                 
>>> print(vp.fov)                                                                                                                                                                                       
0.6108652381980153

The default value is 0.61 (radians), which corresponds to 35 degrees. To zoom in, you should set the FOV parameter to a smaller value, but which is still on the same order of magnitude, e.g. 0.3. Make sure to set the FOV really after setting the projection type, because setting the projection type may reset the FOV value back to its default. Thus, instead of
Code: [Select]
vp = Viewport(type=Viewport.Type.PERSPECTIVE, fov=###)
better do
Code: [Select]
vp = Viewport(type=Viewport.Type.PERSPECTIVE)
vp.fov = 0.3

Note that I haven't tested your code directly. Please check if you can get better results now with the hints I gave. If not, I'll have to take a closer look at your script.

Alexander Stukowski

  • Administrator
  • Hero Member
  • *****
  • Posts: 595
Re: Change zoom (not camera position) in python script
« Reply #5 on: May 14, 2019, 09:55:50 AM »
By the way: I put a new development build of Ovito 3.0.0 online yesterday, which already contains an extension that allows you to control the orientation of the camera's vertical axis:

http://www.ovito.org/manual_testing/python/modules/ovito_vis.html#ovito.vis.Viewport.camera_up

With that, you no longer have to adjust the global application settings to change the orientation of the box in the rendered picture.

benlindsay

  • Newbie
  • *
  • Posts: 5
Re: Change zoom (not camera position) in python script
« Reply #6 on: May 14, 2019, 04:21:36 PM »
Thanks! The fov trick and camera_up parameter seem to be working nicely! It seems like the dev version has some breaking changes relative to changing particle properties though. I didn't include this in my previous script for simplicity, but I have a loop that looks like this:

Code: [Select]
for t in node.output.particle_properties.particle_type.type_list:
    t.radius = 1.78
    t.color = (1.0, 0.0, 0.0)

Which now gives me an error that looks like this:

Code: [Select]
Traceback (most recent call last):
  File "/Users/benlindsay/scratch/dmft-adsorbing-polymer/scripts/generate-pngs-from-long-box-lammpstrj.py", line 44, in <module>
    t.radius = 1.78
RuntimeError: You tried to modify a ParticleType object that is currently shared by multiple owners. Please explicitly request a mutable version of the data object by using the '_' notation.

I'm not quite sure what the '_' notation is referring to here. I tried a bunch of things with no success. Any help would be appreciated.

I also sometimes use a rs.everyNthFrame = 5 kind of thing, where rs is a RenderSettings object, but now that gives an error like AttributeError: 'ovito.plugins.PyScript.RenderSettings' object has no attribute 'everyNthFrame'. What's the updated way to do this?

Alexander Stukowski

  • Administrator
  • Hero Member
  • *****
  • Posts: 595
Re: Change zoom (not camera position) in python script
« Reply #7 on: May 14, 2019, 04:37:45 PM »
Yes, Ovito 3.0.0 brings several changes in the Python interface.

The RenderSettings class has been deprecated, and its use is discouraged. Instead, simply use the Viewport.render_anim() function.

The RenderSettings class is still available in case you want to keep using it. The "everyNthFrame" field has been renamed to "every_nth_frame".

The documentation of the updated Python interface is still incomplete in some places. In particular the '_' notation is not yet introduced anywhere. Probably it isn't needed anyway in your particular case. Currently, Ovito prevents you from modifying the display radius and color of particle types in the pipeline output. This makes sense, because such a change could cause unwanted side effects. The solution is to instead modify the original data that enters the pipeline. This data is cached by the FileSource object delivering it from the input file. See the documentation of the FileSource. One of the example code snippets demonstrates how to configure the display properties of individual particle types. Hope this helps you solve the problem. If not, let me know.

benlindsay

  • Newbie
  • *
  • Posts: 5
Re: Change zoom (not camera position) in python script
« Reply #8 on: May 14, 2019, 07:18:10 PM »
Yep, that fixed it! Thanks so much for you help Alex! For the record for anyone reading this later, the script I settled on after all of Alex's help looks like this:

Code: [Select]
from ovito import dataset
from ovito import version

assert version[0] == 3

from ovito.io import import_file
from ovito.vis import TextLabelOverlay
from ovito.vis import Viewport
from ovito.vis import RenderSettings
from ovito.vis import TachyonRenderer
from ovito.vis import OpenGLRenderer
from PyQt5 import QtCore
import numpy as np
import sys
from pathlib import Path
import os

input_file = Path(sys.argv[1])
output_file = input_file.parent / (input_file.stem + ".png")

# renderer = OpenGLRenderer()
renderer = TachyonRenderer()
node = import_file(str(input_file), multiple_frames=True)
node.add_to_scene()
vp = Viewport(camera_up=(0, 1, 0))
vp.type = Viewport.Type.PERSPECTIVE
vp.fov = 0.15 # was originally about 0.61
data = node.compute()
overlay = TextLabelOverlay(
    text="[SourceFrame]",
    alignment=QtCore.Qt.AlignRight ^ QtCore.Qt.AlignBottom,
    font_size=0.05,
    text_color=(0, 0, 0),
)
vp.overlays.append(overlay)

cell = data.cell
x0 = cell.matrix[:, 3]
xlat = cell.matrix[:, 0]
ylat = cell.matrix[:, 1]
zlat = cell.matrix[:, 2]
boxorigin = x0 + (cell.matrix[:, :3] * 0.5).sum(axis=1)

theta = 20 * np.pi / 180
rorbit = np.linalg.norm(zlat) * 3
pxyz = np.array([rorbit * np.cos(theta), rorbit * np.sin(theta), boxorigin[2]])
dxyz = -(pxyz - boxorigin)
vp.camera_pos = pxyz
vp.camera_dir = dxyz
for t in node.source.data.particles.particle_types.types:
    t.color = (1, 0, 0)
    t.radius = 1.78

range_opt = RenderSettings.Range.ANIMATION
size = (800, 300)
rs = RenderSettings(
    filename=str(output_file),
    size=size,
    range=range_opt,
    renderer=renderer,
    background_color=(1.0, 1.0, 1.0),
)
rs.every_nth_frame = 1
vp.render(rs)

If anyone wants to try this at home, I attached the trajectory file I used this on. You can run it with:

Code: [Select]
ovitos my_ovitos_script.py cat_traj_np.lammpstrj
I also attached an image of one of the frames that was generated from this script. (It generates 40 images.)