GelViz v0.7.0 - Documentation


Table of contents

Introduction

GelViz is a Python-based application for visualizing molecular dynamics simulation data.

As opposed to existing visualization tools, GelViz allows the user to quickly setup data-centric visualization rules which use mathematical expressions to connect the data directly to a wide range of visualization schemes, each with several different attributes.

The founding idea is to allow the usage of a direct mathematical framework for indulging the artistic approach when creating a visualization.

GelViz can also be—and was intended to be—used in conjunction with POV-Ray for producing very high quality visualizations.

Requirements

GelViz was developed using the following free and open-source libraries:

  1. Pyglet 1.2.3, a pythonic OpenGL library for Python. Use pip install pyglet to install
  2. SciPy 0.15.1, matrix tools
  3. NumPy 1.9.2, optimized matrices
  4. PIL 1.1.7, for saving images
  5. ffmpeg (or similar) for assembling frames into a video

Additionally, it is recommended the use of POV-Ray 3.7, or better, in order to create advanced renderings. POV-Ray is also free and open source.

All of these are supported by all the major platforms and operating systems.

Application structure

GelViz utilizes OpenGL to generate real-time, interactive 3D visualizations.

However, due to time and complexity constraints, it was developed using the old, fixed-function pipeline. This means it does not make use of the high level of parallelization in modern GPUs, via the use of geometry, vertex and fragment shaders.

This means two things:

  1. Generating geometry is particularly slow, especially due to the fact GelViz is a Python application. Ideally, geometric impostors would be used instead, having the shaders doing most of the work.
  2. No shading was implemented within GelViz. Instead, the advanced rendering features were delegated to POV-Ray.

For these reasons, GelViz can be rather slow when generating the geometry of a visualization. To compensate, the results are cached so reuse is instantaneous.

Modules

The program consists of several modules separated in multiple files. For specific implementation details, see the comments in the respective source codes. Most modules do not warrant a full detailed explanation, as they are not meant to be used directly.

gelviz

GelViz

The main application class. This class handles the viewports, controls and rendering management.

camera

Camera

An object with the camera properties. Each Viewport instance has an associated camera object.

Camera

The Camera class stores information about a viewport's camera settings. These include position, rotation, field of view, etc. The default settings are usually adequate for most visualizations.

The current viewport's camera can be retrieved by pressing C while GelViz is running. This outputs the camera's position to the console. The resulting data can be used to write a script such as

viewport.camera.xyz = (3.590228,-2.267513,62.986461) viewport.camera.rx = 12.2 viewport.camera.ry = -35.2

which can be helpful to define a new standard camera setting.

color

Color

Handles color functions such as mixing and different color spaces.

ColorSet

Compactly stores several colors in a way that's ready to submit to OpenGL.

Color

The Color class is an abstract representation of a RGBA color, with the A standing for alpha transparency. The default constructor allows the creation of an object representing a RGBA color with components from 0 to 1. For example, Color(1,0.5,0,0.5) represents a half-transparent orange color. If not specified, the alpha component is taken to be 1 (fully opaque).

The class also includes static constructors for the HSV color space, or the same space with transparency, HSVt. Color.HSV(1.0/3.0, 1, 0.5) represents a saturated dark green (hue = 0.333..., value = 0.5). To specify transparency, invoke the Color.HSVt method: Color.HSVt(1.0/3.0, 1, 0.5, 0.5). Colors can be added component-wise by addition (e.g.: color1 + color2) and multiplied by scalars. All operations are performed in the internal RGBA color space.

In Python, classes are objects and are defined only once. As such, the Color class can be modified globally to perform certain global changes. The class contains a few variables that can be used.

Color.BRIGHTNESS_CORRECTION, defaulting to zero, is a color-correction term that globally increases the luminance of all colors, as well as making the colors more opaque. It is an aggressive color-adjustment that shouldn't be used except in extreme situations.

Color.GAMMA, defaulting to 2.2, adjusts the gamma used to compute a color's perceptual brightness (luminance).

curves

Defines several functions mapping the unit interval onto itself in a non-linear way. Useful for non-linear ramps and windowing.

For instance, the function window specifies a polynomial bump window that can be used as the fading parameter in a gradient. This would hide from view all entities that don't fall within a window.

The curves can be used to accentuate the extremes of a data by modifying the mapping in the gradient.

dataset

Defines general input data containers. A "dataset" is defined as a collection of "snapshots", which in turn contain an atoms property which points to an instance of an Atoms class, which holds a NumPy array with the actual data.

Dataset

Generic dataset description. Different import file formats should be extensions of this class.

Snapshot

Container for one snapshot of data.

Atoms

Container for the actual data per atom. The class is intended to normalize the format of the raw data in the NumPy array.

BoundingBox

Describes the bounding box for a Snapshot.

CustomAttributes

Handles custom composite attributes.

Dataset

Datasets can be queried with GelViz expressions using the query method, which takes any valid GelViz expression. This is useful to see what values will be used for an expression. The following code queries the dataset for the local minimum value of c_stress4 in snapshot number 5.

dataset.query("$lmin:c_stress4", 5)

This could equivalently be found by invoking

dataset[5].dataRange['c_stress4'].min

event

Defines a basic event dispatcher used for passing keyboard and mouse events around. Check source code for implementation.

geometry

Vector

3D Euclidean vector and related functions.

Quaternion

Implements quaternion algebra, which are much more efficient and superior method to handle rotations instead of using matrices.

gradients

Defines a series of gradients and gradient modifiers to be used within GelViz. Gradients are objects that map a collection of real numbers to one single Color object. Run gradients.py on its own to generate and view a preview of all available gradients. The default file generated is shown below:

The columns represent: gradient over 50% gray background with no fade, black with variable fade, white with variable fade and the luminance map for the gradient. Notice that not all gradients have uniform or linear luminance, which can be problematic for certain visualizations.

For example, the default Jet, as used in Matlab and several other plotting packages, has a very uneven distribution of luminance, which results in the majority of the visual intensity to be concentrated on the yellow-green-cyan section, visually accentuating the middle values. The uniform JetU normalizes the entire gradient near the lowest available luminance, giving a darker gradient but with constant luminance, ideal for a qualitative display.

The gradient Spectrogram also includes a fading ramp as well as value ramp. This means lower values are automatically faded-out.

It is very easy to create new gradients. See section Colors and gradients for a tutorial and more details.

input

Contains the different file format parsers for loading datasets. These contain specialized extensions of the Dataset and Snapshot classes found in the dataset module. The abstract classes handle file access and caching, while the specialized classes handle the parsing of the lines.

lammps

Parses and imports LAMMPS dump data. Includes specializations of the Dataset (Dump) and Snapshot classes.

Dump

Manages a LAMMPS dump file, by parsing and creating the appropriate data structures.

Snapshot

Manages the LAMMPS metadata associated with a snapshot (such as bounding box, atoms, snapshot timestep, etc.)

See LAMMPS's documentation for file format details.

table

Parses and imports table dump data. Includes specializations of the Dataset and Snapshot classes.

File format consists of headers followed by data per atom. Example:

id x y z 0 0.2342 0.5321 0.5542 1 1.2133 2.2753 2.1885 2 2.0189 6.2340 1.2332 ...

Any number of columns may be specified, but GelViz will seek for x y and z for positioning purposes. If not found, an error will occur.

Multiple headers define multiple snapshots. Headers must be consistent across a file: a snapshot cannot have a different header from another snapshot in the same file.

Important: features that rely on multiple snapshots may not work properly if an ID is not specified, since one is automatically assigned and not guaranteed to be trackable to the same particle. Similar problems will arise if the number of particles changes between two snapshots.

As bounding box is not defined in the file, an orthogonal bounding box is automatically assigned.

layers

Defines the different visualization layers supported by GelViz.

LayerManager

Simple object to manage layer instances.

Layer

Abstract Layer class defining core features.

BoundingBoxLayer
AtomsLayer
BondsLayer
VectorsLayer
TrajectoriesLayer

Layer names are self-evident

For usage, see Setting up the visualization layers

povray

Support for exporting visualizations as a POV-Ray SDL .pov file.

POVRayFile

Writes the POV-Ray file template and manages frames.

POVRayFrame

Merely a container for the lines corresponding to the extra POV-Ray files generated for each snapshot, with some additional metadata.

primitives

Defines the geometric primitives (which subclass scene.Entity) used when rendering a scene. Objects are delegated the responsibility of drawing themselves.

LineNetwork

Gel network as a collection of line objects. Fast to generate.

TubeNetwork

Gel network as a collection of tubes. Slow to generate.

Line

A simple OpenGL line between two points. Colors are defined at the edges and interpolated in between. The line does not scale with proximity to camera.

Tube

An open cone between two points defined by the points and two radii. If the radii are the same, the tube is a cylinder. In GelViz, tubes are rendered as hexagonal (by default) conical prisms approximating cones or cylinders.

Arrow

An arrow comprised of a cylinder and a cone.

Box

A triclinic box defined by three lattice vectors a, b, and c.

Point

An OpenGL point. Point size is global and cannot be altered per atom. Limitations of the size are dictated by hardware. For fine control, use spheres.

Sphere

A sphere defined by a location and a radius.

Path

A collection of points and colors, joined by a line.

processing

Defines routines and objects that process data.

Bonds

Computes bonds between particles based on a cutoff distance. Additional metadata such as coordination number is also computed and accessible.

scene

Defines hierarchical entities for a scene graph structure. Scenes are created as a collection of nested containers, which ultimately contain entities to be drawn.

Container

General container object. Containers have their own OpenGL matrix, which allow for transformations of all containing objects or sub-containers.

Entity

General entity object. An entity represents the final, drawable object.

Scene

The root container object.

tools

Defines several useful functions to be used across GelViz.

sortAtoms

Naturally sorts a list of strings based on the integer in the first word. This is used internally to create a direct association between atom id and row number in the NumPy array containing the atom data.

clamp

Forces an input number to be inside an interval. For an interval [a,b] and a number x, if x < a, returns a, and if a > b, returns b. Otherwise, x is in the interval and returns x unchanged.

normalize

Maps one interval into another using linear interpolation. Usually used to map intervals to the unit interval [0,1], but this can be specified as well. See Attributes and modifiers.

symmetrize

Similar to normalize, except it maps 0 to the center of the target range, maintaining the proportionality. See Attributes and modifiers.

polarize

Similar to symmetrize, but maps the negative and positive values differently. The smallest negative value is mapped to the smallest value in the target interval, and the largest positive value is mapped to the largest value in the interval, while keeping zero at the center. Zero maps to the center of the target interval. This means the scale of negative and positive values is different. This is useful for qualitatively accentuating the positive and negative values separately, when they coexist. See Attributes and modifiers.

getDataRange

Used internally to get the intervals in which each atom attribute lies.

getMean

Returns the arithmetic mean for each atom attribute.

buildExpression

Internal use only. Transforms custom expressions involving atom attributes into Python representations internal to GelViz.

getFunction

Transforms a string into a function and returns the function object. Data expressions are internally substituted by hashed variable names for optimization (e.g.: @c_stress1 becomes _ca73220a632553e0dc53f69dee65a59e.)

viewport

Viewport

The viewport object, which holds information about the GelViz windows, their viewing cameras and other attributes.

Creating visualizations

GelViz has no advanced graphical user interface. It is instead a visualization framework you can use to visualize data. The idea is to create visualization scripts that run on top of GelViz that can be used and reused to visualize data in the same style. Additionally, the interactive visualizations in GelViz are rather limited in term of rendering style. To compensate for this, GelViz can output the current visualization as series of easily configured POV-Ray files to produce very high quality renderings. This is the intended workflow by design.

To create a visualization, you must create a Python script that invokes GelViz and its functions to setup the visualization as desired. Once this is done, this script can be reused for future visualizations in the same style.

Datasets

GelViz works with datasets in form of text files. By default, two formats are supported: LAMMPS dumps and generic tables. Dataset files should include one or more "snapshots", which represents a moment in time in which data was recorded.

Each snapshot contains a list of atoms and several values associated with each atom at a given time. These values are referred in GelViz as atom attributes. Atom attributes are referenced in GelViz expressions by prefixing the attribute name by an @ (at) symbol.

Basic setup

First of all, we import the GelViz application.

from gelviz.gelviz import GelViz

Next, we import the LAMMPS input module and the gradient definitions. Additionally, we might want to specify a color directly, so we import the Color object as well.

import gelviz.input.lammps as lammps import gelviz.gradients as gradients import gelviz.color as Color

Next, we create a GelViz instance, load the data using the input object and bind it to the GelViz instance.

viz = GelViz() path = "some_data_path.lammpstrj" dataset = lammps.Dump(path, load_all=False, auto_save=True) viz.dataset = dataset

For details on the input formats, see section on the input module.

It is not necessary to define path or dataset as separate variables, of course, but that can be helpful.

To further modularize the usage of your visualization, it is helpful to create a command-line interface in our script. This can be achieved by the following code.

import sys path = sys.argv[1] # uses the parameter passed to the script as a filename

(Naturally, import sys should be somewhere on top of the file along with other import statements, for consistency.)

Next, we create a viewport.

viewport = viz.createViewport( width=1024, height=768, background_color=Color(0,0,0) )

The method createViewport() creates a window with a 3D OpenGL context. Any number of viewports may be created with different dimensions and background colors. Each viewport has an associated camera that can act independently. This means you are free to create multiple views of the same scene.

By default, a camera is initialized at position (0,0,1). We can manually set up the location of the camera and its rotation relative to the scene.

viewport.camera.z = 70 viewport.camera.rx = 45 viewport.camera.ry = -45

Changing the camera's x or y coordinate pans the image inside the viewport. Changing only the z coordinate merely backs off from the scene so we can see it better. If you wish to automatically view the whole scene, instead of changing the z coordinate directly use

viewport.view(dataset[0])

This function gets the first snapshot in the dataset (dataset[0]) and uses its bounding box to find the best viewing distance automatically. This is the preferred method, as it also sets up the fog so it adapts to the current dataset's dimensions.

Otherwise, manually changing viewport.fogRadius to a different value may be needed.

Expressions

For defining visualization properties, atom attributes may be referenced using the attribute notation described in this section.

GelViz expressions are strings containing any function from the standard math module, the curves defined in GelViz's curves module and any arithmetic expression involving the atom attributes as defined below. A GelViz expression is a mathematical expression involving the data being visualized, and should be thought of merely a way of describing a real number.

As such, numbers (int, long and float types in Python) are also considered expressions, which just happen to have a constant value.

Attributes and modifiers

Atom attributes are referenced by using the @ (as in attribute) character before the attribute name. For instance, for referencing the 'xu' attribute of an atom in an expression, you would write @xu.

Attributes are per-atom and from the current snapshot being visualized. You can access the same information for the same atom in a different snapshot by using a seek parameter. For example:

For example, if you wish to use the 'x' displacement between two snapshots, you could specify @+1,x - @x.

Important: Seeking snapshots will inevitably result in trying to fetch data from snapshots beyond the available range. For instance, using @-10,x, if visualizing the first snapshot (number 0), would be asking for the 'x' position in snapshot -10, which does not exist. To avoid complications and errors, GelViz will use the nearest available snapshot instead, that is, it will use the first snapshot for any negative snapshot being requested, and similarly for positive offsets beyond the last snapshot. You should consider this when rendering an animation, as snapshots where this over-seeking occurs will present data that may be unexpected. These should probably be left out of the analysis.

In addition to seeking, one can apply several modifiers to the requested data. This is achieved by the modifier syntax: @mod:attribute. The modifiers can be "local" (l) or "global" (g) or "custom" (c). Local modifiers utilize the data range across the current snapshot, while global modifiers utilize the data range across the entire dataset and all snapshots.

Custom modifiers allow you to specify a custom range to perform the linear mapping of the data. See below for an example. Values that fall beyond the specified interval will be proportionally scaled, and fall outside the unit interval [0,1]

The modifiers are:

Here's a visual example of the behavior of normalize, symmetrize and polarize applied with the default target unit interval [0,1]. The data range is represented by a series of rectangles, which shall be mapped to the [0,1] interval. For normalize, the sign of the data is irrelevant. The sign is taken into account in the other functions.



See tools.py for the relevant functions.

Examples:

Seekers and modifiers can be used in conjunction as well, so this is perfectly valid:

@+10,cn(-10,10):x: from 10 snapshots ahead, get the value of 'x' for this atom normalized using the range [-10,10].

Important: due to internal limitations of Python and NumPy, if your dataset defines an attribute such as c_br_atom[2] it will be internally translated to c_br_atom2, removing the brackets. This happens since the internal representation of these attributes must be a valid Python identifier. To see the list of attributes after importing a file, use print dataset[0].atoms.data.dtype.names where dataset is the Dataset object for the current data.

Composite attributes

It is often desirable to use a collection of attributes to compute a new quantity of interest for each atom, such as the magnitude of the displacement vector. To achieve this, GelViz allows the definition of composite attributes.

Composite attributes are defined using the define method on a dataset object, and use the # symbol instead of the @ symbol for being referenced. For example, to compute the displacement in the x-direction between the current snapshot and the next.

dataset.define("dx", "@+1,xu - @xu")

Once defined, this value can be referenced in any future GelViz expression by writing #dx. The following code defines displacements in all three directions and use these new definitions to compute the magnitude of the displacement vector, dr.

dataset.define('dx', "@+1,xu - @xu") dataset.define('dy', "@+1,yu - @yu") dataset.define('dz', "@+1,zu - @zu") dataset.define('dr', "sqrt(#dx**2 + #dy**2 + #dz**2)")

Important: the order of these definitions is important. Composite attributes are computed internally in the order they are defined to guarantee consistency.

These new attributes can be now used in GelViz expressions just as any other atom attribute, with seekers and modifiers, but with some minor restrictions. For instance, we can now color atoms by the magnitude of their displacement.

viz.layers.atoms.color = gradients.JetU("#gn:dr")

Important: However, to perform a global normalization (gn) based on the displacement, GelViz must not only load all the snapshots, it must also compute the value of dx, dy,dz and dr for all atoms of all snapshots before rendering. This can take a significant amount of time to execute.

Some performance gains can be obtained by not defining attributes that will only be used for defining other attributes. For instance, the above would be better specified as:

dataset.define('dr', "sqrt((@+1,xu - @xu)**2 + (@+1,yu - @yu)**2 + (@+1,zu - @zu)**2)")

This avoids computing and storing 3 composite attributes for each atom of each snapshot, which is a good performance gain for larger datasets.

One can also seek data using composite attributes. The expression #+1,dr fetches the value of dr from the next snapshot. Since the definition of dr itself requires information from a snapshot further in time, the expression #+1,dr requires loading and computing data for 3 snapshots.

Important: Since composite attributes are computed all at once for a snapshot that is referenced, definitions with seeking can result in GelViz computing the data for all snapshots due to a chain reaction.

Properties

The local or global minimum or maximum of attributes may also be referenced in a GelViz expression directly by using a property. Properties are marked by the $ character. The only available properties are:

Seeking can be performed in local properties, as in $+1,lmin:x.

Predefined variables and functions

Within the scope of GelViz expressions, a few variables and functions are defined.

In addition, all constants and math functions from the standard math package will be available, as well as the functions from GelViz's curves module.

Colors and gradients

Once the geometric attributes for the visualization are set, we need to set the color attributes. This can be done by passing a Color attribute or a Gradient object of some type to the color parameter of a layer.

Most humans can perceive a wide gamut of colors due to the presence of three cone cells, which are sensitive to certain bands of light from the electromagnetic spectrum. The three cone cells peak in absorption around red, green and blue, as the diagram below shows.


Absorption spectra for short (blue), medium (green) and long (red) wavelength for the cone cells. Source (Public Domain)

However, the perceived brightness of each color is very different. By a large margin, green is perceptually brighter than red, which in turn is brighter than blue. This results in a non-uniform distribution of brightness across the color gamut depending on the hue, with a distinct peak on yellow and cyan. The image below illustrates the luminance per hue, where this effect can be easily observed.

Such quirks of human color perception must be taken into account when developing a visualization, as these colors may artificially emphasize parts of the data that should not hold particular interest by themselves. This is a very common pitfall of many modern scientific plots and visualizations, particularly of those using the standard "Jet" color map (found in Matlab), reproduced below.

Even more so, there is limited amount of quantitative information that can be conveyed by color or brightness. The human eye and visual cortex are not accurate enough to extract all the information that may be encoded in a color, and nearby colors play a critical role in color perception. The optical illusions below illustrates this well: (left) squares A and B have exactly the same brightness, but are perceptually very different. (right) the blue and green colors are actually the same shade of light green.


Source (Copyleft)

© Akiyoshi Kitaoka 2009

It is necessary to keep these issues in mind when making a visualization. On the one hand, we need to convey the data as clearly and plainly as possible. On the other hand, we need to highlight the details that are important by means of color and shading.

The role of the person creating a visualization is then to properly map one type of information to the other. If the information is numerical, this translates to a set of mathematical functions mapping data to shape attributes and colors. The shapes GelViz can create were already covered in a previous section, so this section will now focus on setting up the colors for a visualization.

GelViz offers a very flexible and powerful interface for picking up colors out of an arbitrary color space. At its most basic, we can define a single constant color to any of our layers. See the section on colors for details.

viz.layers.atoms.color = Color(1,1,0,1) # constant color (yellow) viz.layers.bonds.color = Color.HSVt(2.0/3.0,1,1,1) # constant color (blue)

It is usually more useful to map values to a color space. This is done by employing an Gradient object.

The purpose of the Gradient object in GelViz is to determine an N-dimensional parameter space that is surjective onto the RGB color space. When initialized, these gradients will receive up to N GelViz expressions, which will be computed into values that will be used to generate the final color.

The most general default gradients are RGBA and HSVt, which are four-dimensional (and therefore impossible to visualize). Each parameter is converted directly into a color component in the RGBA or HSVt spaces. The following code assigns a color based on the normalized coordinate of an atom, and the transparency is assigned based on the square of the normalized xy component of the stress tensor.

viz.layers.bonds.color = gradients.RGBt( "@ln:x", "@ln:y", "@ln:z", "@ln:c_stress4**2.0" )

High-dimensional gradients can be useful, but they are hard to master and impossible to preview. Usually, gradients with 1 or 2 dimensions are good enough, and these can be readily visualized.

GelViz comes with a few predefined 2D gradients. The first parameter gives a color in a linear spectrum. The second parameter is a fading term, which controls the transparency of the resulting color separately. Both parameters should be in the unit interval [0,1], which greatly simplifies things. Some gradients include transparency even with full opacity specified (e.g.: PolarizedAlpha, Spectrogram). The fading parameter is optional in all of the default gradients, which are shown below.

This preview can be generated by running the gradients.py file directly. The columns represent: gradient over 50% gray background with no fade, black with variable fade, white with variable fade and the luminance map for the gradient. Notice that not all gradients have uniform or linear luminance, which can be problematic for certain visualizations.

Gradient modifiers

It is sometimes necessary to make minor adjustments to the gradients in order to adjust overall brightness, contrast or opacity. In GelViz, these adjustments can be made by means of gradient modifiers.

Modifiers are special methods available for any gradient that result in a new modified gradient. As such, modifiers can be chained to perform multiple modifications on an original gradient. For example, here's a gel network with the Spectrogram gradient, which we wish to modify.

viz.layers.bonds.color = gradients.Spectrogram("@ln:x")

This gradient will be at the left in the examples that follows, for comparison.

The hue of all colors in the gradient can be adjusted with the hue modifier. Since hue is cyclic and the hue value ranges from 0 to 1, this offset can be anywhere from -1 to +1. For example, if we wish to offset the hue so that red (0) becomes green (1/3), we apply the modifier as follows.

viz.layers.bonds.color = gradients.Spectrogram("@ln:x").hue(1.0/3)

The gradients Polarized and PolarizedAlpha are intended to maximize the perceptual difference between negative and positive values being visualized. This is done by creating a gradient with complimentary colors (red vs cyan). To obtain such pair for colors other than red/cyan, use the hue modifier.

viz.layers.bonds.color = gradients.Polarized("@ln:x").hue(0.3).saturation(1)

The gradient can be tinted with another color using the tint modifier. A color must be specified, as well as a value representing a percentage of the color that must be tinted. In the following example, the gradient is tinted green by 30%.

viz.layers.bonds.color = gradients.Spectrogram("@ln:x").tint(Color(0,1,0), 0.3)

The saturation of the gradient can be adjusted using the saturation modifier. The amount of adjustment is modulated by a number that ranges from -1 to 1. A value of -1 indicates maximum desaturation, and a value of +1 indicates maximum saturation. A value of zero indicates no change.

viz.layers.bonds.color = gradients.Spectrogram("@ln:x").saturation(-0.5)

The brightness of the gradient in the HSV color space can be adjusted using the value modifier. This modifies the brightness of the color without changing its hue or saturation. As before, the adjustment factor ranges from -1 to +1.

viz.layers.bonds.color = gradients.Spectrogram("@ln:x").value(1)

The perceptual luminance of the gradient can be adjusted as well. This modifies the perceptual brightness of the color, but it will also change its hue or saturation to achieve this. As before, the adjustment factor ranges from -1 to +1.

viz.layers.bonds.color = gradients.Spectrogram("@ln:x").luminance(1)

The transparency of the color significantly affects its perceptual brightness as well, as in the previous case. We can force colors to be more transparent or more opaque by using the alpha adjustment. As before, the adjustment factor ranges from -1 to +1. alpha(1) makes the entire gradient opaque.

viz.layers.bonds.color = gradients.Spectrogram("@ln:x").alpha(1)

Finally, gradient modifiers may be chained to create entirely new gradients. The order of the modifiers should be read from left to right, and operations are not commutative.

viz.layers.bonds.color = gradients.Spectrogram("@ln:x").luminance(0.5).saturation(1).tint(Color(1,0,1),0.4)

Inverting gradients

Since gradients in GelViz are not limited to be one-dimensional, there's no guaranteed way to invert them in general. However, since the parameters passed to default gradients are in the unit interval [0,1], it is trivial to invert them.

If our gradient is taking the normalized value @gn:x, we can just subtract this from 1 to obtain the inverted gradient.

gradients.Jet("1-@gn:x")
Global color brightness

Sometimes, colors within GelViz may be perceived as being too dark. There are several ways to deal with this situation. The recommended way is to simply tweak the colors and gradients using the modifiers mentioned previously. Usually, best results are achieved by using alpha, value and luminance.

The most aggressive (and not recommended) way is by globally tweaking the brightness of all colors by overriding the BRIGHTNESS_CORRECTION property of the Color class before a GelViz instance is created. This will globally adjust the luminance and alpha transparency of colors to perceptually increase their contribution to the final render.

Color.BRIGHTNESS_CORRECTION = 0.3

For instance, here's a comparison of the effects of setting this value to 0.0 (default), 0.3 and 1.0.

Valid values range from 0 to 1. When set to 1, transparency is disabled, all colors have the maximum saturation possible, and the only grayscale colors will be black and white. Notice, however, that the fog will still affect the fully bright colors the same way as before. This only changes the internal definitions of the colors to adjust the brightness.

Creating new gradients

A new gradient can be created by writing a new class extending the Gradient or ColorMap classes.

The Gradient class allows the creation of a N-dimensional gradient. The __init__ constructor receives N parameters (other than self), which should be converted to function references using the getFunction function available in the tools module. Then, the getColor method should be defined with exactly three parameters, dataset, sn and i, which will be the current data set, snapshot number and atom ID, respectively. The return value must be a Color object.

The idea is that the input parameters to the gradient define what format the input data will take. The resulting value of these input expressions is then used internally by the gradient to compute a specific color, which is then returned.

class Reds(Gradient): def __init__(self, v1, v2=1): self.f1 = getFunction(v1) self.f2 = getFunction(v2) def getColor(self, dataset, sn, i): a = self.f1(dataset, sn, i) b = self.f2(dataset, sn, i) return Color.HSVt(0.5/3.0*a**2.0, 1, 0.25+0.75*a, b)

Attempting to retrieve a color beyond the unit interval may return unexpected results, depending on the arithmetic performed to map the parameters to a color.

The ColorMap gradient defines a 2-dimensional gradient. The first parameter, in the unit interval [0,1], defines a position in a spectrum of colors generated by interpolating any number of generating colors for the gradient. The second parameter tunes an alpha transparency "fading" factor of the resulting color.

The gradient is defined by first invoking the constructor of the ColorMap class via the super statement, followed by an arbitrary number of color-stops defined via the addColor method. The first parameter defines the position of the color stop on the unit interval [0,1]. The second parameter is the color at that position in the gradient's spectrum of color. Colors are linearly interpolated in between color-stops, so results may need to be fine-tuned for clarity. See example below.

class RedGreenBlue(ColorMap): def __init__(self, v1, v2=1): super(self.__class__, self).__init__(v1, v2) self.addColor(0.0, Color(1,0,0)) self.addColor(0.5, Color(0,1,0)) self.addColor(1.0, Color(0,0,1))

Attempting to retrieve a color beyond the unit interval automatically clamps to the nearest defined color.

An individual gradient can be previewed (with respect to its first parameter) by invoking the static method preview(), as in gradients.JetU.preview().

Possible issues / known problems

Rendering transparent objects is a difficult issue in computer graphics. This is due to the fact the colors viewed through a transparent object are modified by the color of it, so the colors to be blended need to be computed in the correct order: from the back to the front.

When multiple transparent objects are considered, as in the case of GelViz, some issues can occur, such as transparent objects shown in front of opaque objects not looking transparent, as shown in the following example, where dark dots are shown on top of opaque, bright dots.

To fix this issue, transparent objects would have to be sorted and drawn on top of opaque objects in reverse order, starting from the back. Sorting thousands of objects in such a way is prohibitive. There are advanced techniques to overcome this issue, but GelViz does not implement them.

For most visualizations, this may not be a problem. If it turns out to be a critical issue, consider removing transparency altogether and working around it with creative use of colors and angles.

This issue is only present in 3D renderers that use rasterization of polygons, as is the case of modern day GPUs. This problem is not present when rendering using POV-Ray, as it uses an entirely different approach to 3D rendering (raytracing).

Setting up the visualization layers

GelViz visualizations are defined in layers. Each layer represents some information of the data you can visualize. Currently, 5 layers are supported. If viz is a GelViz instance, as in the previous examples, the layers can be accessed via

viz.layers.box # visualizes the bounding box viz.layers.atoms # visualizes the atoms viz.layers.bonds # visualizes the bonds viz.layers.vectors # visualizes vectors viz.layers.trajectories # visualizes trajectories

Layers are disabled by default, except for the bounding box, which is enabled by default. To enable a layer, you must call the enable() method, as in viz.layers.atoms.enable().

Each layer has a series of parameter which are used to setup the visualization. For instance, we can specify a uniform color red for all atoms via the code.

viz.layers.atoms.enable() viz.layers.atoms.color = Color(1,0,0)

Bounding box layer

The box layer visualizes the current snapshot's bounding box, defined by three lattice vectors a, b, c. These may be specified in the data, or inferred from the coordinates. Following LAMMPS's standard, one of the corners of the box lies at the origin, from which the vectors define the other corners.

The bounding box is drawn using OpenGL lines of thickness specified by the lineWidth parameter, which correlates with pixels and are invariant with distance to the camera. The color of the bounding box is specified by color, which must be a Color object. For instance, the following code defines a semi-transparent yellow bounding box.

viz.layers.box.lineWidth = 2.0 viz.layers.box.color = Color(1,1,0,0.5)

Notice that we do not need to call box.enable(), as it is enabled by default. By default, the bounding box is solid white with line width of 1 pixel.

Important: If a position transformation is applied, the box will not change shape. It is non-trivial to deform a bounding box arbitrarily for any transformation. The alternative would be to replace the bounding box by an orthogonal one, but that is not meaningful if a bounding box was originally specified for the data.

Atoms layer

The atoms layer visualizes the atom positions. The positions are taken from the 'x', 'y', and 'z' attributes in the dataset, which are periodic (wrapped).

Atoms can be visualized in two styles: "point" or "sphere", points being the default setting. Points are much faster to generate and render than spheres, but present some limitations.

If the style is set to "point", the available settings are pointSize, which must be a number specifying the width of the point, and color, which can be a number or an GelViz expression. For this style, the point size is uniform for all particles and invariant with distance to the camera. Important: Different graphics cards will have different limits for the point size. For accurate control of point size, use the "sphere" style

For example, the following code defines the point style and colors the atoms using the Jet gradient based on the normalized x coordinate, with transparency based on the normalized y coordinate.

viz.layers.atoms.style = 'point' viz.layers.atoms.pointSize = 5.0 viz.layers.atoms.color = gradients.Jet("@ln:x", "@ln:y")

For more flexibility for the price of performance, you can choose to visualize the particles as spheres. In this case, the available settings are sphereRadius and color.

With spheres, the radius of individual particles may be defined by a GelViz expression. The following code scales spheres based on the normalized y coordinate.

viz.layers.atoms.enable() viz.layers.atoms.style = 'sphere' viz.layers.atoms.sphereRadius = "0.5 * @ln:y" viz.layers.atoms.color = gradients.Jet("@ln:x")

Bonds layer

The bonds layer draws lines or tubes connecting the locations of the atoms. Bonds are inferred from the distance between neighboring atoms based on a global cutoff distance that can be specified.

Bonds can be visualized in two different styles: "line" or "tube". When configuring the bonds layer, a color can be specified. This color is computed for each end of the bond, and the color along the bond is linearly interpolated between these two color, creating a smooth transition. However, this transition will not correspond to the transition as in a specified gradient: if one end is associated with the first color in a gradient and the other end to the last color, the intermediate colors in the bond will not run through the whole gradient. This feature was planned, but not implemented.

With the "line" style, simple OpenGL lines are drawn between the position of the atoms. The line width may be configured with the lineWidth parameter, which should be a number. This line width will be used for all bonds and is invariant with distance to the camera. Lines are fast to generate and render, but there is limited customization.

The following example creates line bonds colored via the Jet gradient based on the normalized x coordinate. Notice the invariance of the line thickness with proximity to the camera.

viz.layers.bonds.enable() viz.layers.bonds.style = 'line' viz.layers.bonds.color = gradients.Jet("@ln:x") viz.layers.bonds.cutoff = 1.3 viz.layers.bonds.lineWidth = 2.0

For more advanced bonds, with the price of performance, one can instead use the "tube" bond style. Instead of using lines, tube bonds connect the two locations by means of a truncated cone. The radii of the edges of the cone can be defined in any way desired by means of the tubeRadius property, allowing for variations in the bond thickness. If a constant value is defined, the tubes become cylinders. Note that GelViz approximates cones using a 6-sided conical prism by default. For a higher quality rendering, use the POV-Ray output feature instead.

Important: strictly speaking, what is defined is the radius of a sphere at the location of each atom in the bond. The cone is created in such a way that it is tangent to the two spheres, so that the surface can be made continuous with a continuous derivative (see images below). This means that if the distance between the two particles is too small with respect to the radii, there will not be such a doubly-tangent cone and the behavior of GelViz will be erratic. There's no easy or obvious way out of this scenario, but such extremes represent degenerate geometries that shouldn't be utilized.

The property tubeCaps controls whether to draw the caps of the cone. This option is set to False by default.

The following code creates the same visualization as the previous one, except with tube bonds with fixed radius. Compare the close-up images.

viz.layers.bonds.enable() viz.layers.bonds.style = 'tube' viz.layers.bonds.color = gradients.Jet("@ln:x") viz.layers.bonds.cutoff = 1.3 viz.layers.bonds.tubeRadius = 0.05

The following code illustrates an advanced use of the tubeRadius and color parameters.

viz.layers.bonds.enable() viz.layers.bonds.style = 'tube' viz.layers.bonds.color = gradients.Jet("(sin(@ln:x*pi)*sin(@ln:y*pi)*sin(@ln:z*pi))**2.0") viz.layers.bonds.cutoff = 1.3 viz.layers.bonds.lineWidth = 2 viz.layers.bonds.tubeRadius = "0.5*(sin(@ln:x*pi)*sin(@ln:y*pi)*sin(@ln:z*pi))**2.0" viz.layers.bonds.tubeCaps = True

Here's a close up view of the above visualization showing the difference between tubeCaps being enabled (left) or disabled (right).

In addition to these properties, the POV-Ray specific property tubeMerge is also available. It is a boolean value that by default is set to False. This property only replaces the POV-Ray keyword union with merge in the geometric boolean statement describing the network of bonds.

Within GelViz, when any geometric object has some level of transparency and overlaps other objects, both objects will be rendered in the overlapping region. This is particularly visible in the case of bonds, as shown in the image below (left). The net effect is that the location where multiple bonds meet may become more pronounced, creating unnecessary and undesirable visual noise. One way to counteract this effect is to disable tubeCaps, as shown below on the right.

Exporting this visualization to POV-Ray and modifying the generated POV-Ray source code as below, we obtain the equivalent of the two images above. (See POV-Ray settings for more information.)

#declare BOND_ROUNDED = false; // this is enabled by default #declare BOND_CAPS = true; // or false. This is disabled by default

As you can see, this problem will stil occur in POV-Ray.

Since POV-Ray allows for more precise rendering, we can instead use spheres at the edges of the bond tubes to give them a more aesthetically pleasing look. This is what the BOND_ROUNDED option does, which is enabled by default. However, the problem will be aggravated in this scenario, as the overlap will be in the form of a sphere now (below, on left). By setting tubeMerge to True in the bonds layer within GelViz, the POV-Ray file will be exported with the directive to merge the geometry of these spheres to the geometry of the tubes.

The result is a solid, contiguous geometrical structure that gives the desired result (below, on right).

Important: this option is only necessary if transparency is used in the network, otherwise it does not make any difference. The merge directive in POV-Ray can significantly slow down rendering time, so use this as a last resort.

A final setting of the bonds layer defines whether or not to use wrapped coordinates. The wrapped setting is set to True by default. If a position transformation is used, problems may arise. To compensate for this, set the property to False. This makes GelViz use the unwrapped coordinates (assumed to be 'xu', 'yu' and 'zu') to compute the particle distance.

Vectors layer

The vectors layer visualizes a vector field, with vectors irradiating from each atom position in a defined direction.

Vectors can be visualized in two different styles: "line" or "arrow". The "line" style is the default and much faster than the "arrow" style. For either style, the parameter vector must be set to a 3-tuple of GelViz expressions, which will define the x, y and z components of the vectors. The parameter scale can be used to scale vectors, and can also be a GelViz expression.

For the "line" style, the lineWidth parameter defines the thickness of the line. This behaves similarly as the same parameter for the bonds layer.

The following code generates a simple vector field in the "line" style.

viz.layers.vectors.enable() viz.layers.vectors.style = "line" viz.layers.vectors.color = gradients.Jet("@ln:x") viz.layers.vectors.vector = ( "(@ln:y-0.5)", "-(@ln:x-0.5)", "0" ) viz.layers.vectors.scale = "4*@ln:z" viz.layers.vectors.lineWidth = 2.0

The syntax the "arrow" style is similar, with a few other parameters being available.

viz.layers.vectors.enable() viz.layers.vectors.style = "arrow" viz.layers.vectors.color = gradients.Jet("@ln:x") viz.layers.vectors.vector = ( "(@ln:y-0.5)", "-(@ln:x-0.5)", "0" ) viz.layers.vectors.scale = "4*@ln:z" viz.layers.vectors.arrowWidth = 0.1

There are several parameters controlling the visual of the arrows:

arrowWidth defines the thickness of the arrow body.

arrowHeadLength defines the length of the head of the arrow. Must be fine-tuned for thick arrow widths.

arrowHeadAngle defines the internal angle (in degrees) of the cone that constitutes the arrow's head. Default is 40 degrees. Must be fine-tuned for thick arrow widths.

flip specifies whether or not flip the direction of the arrow, so that the head of the arrow not outwards but inwards. To flip the overall direction of the arrow, simply define a vector with negative components instead.

As arrows get smaller, they begin to shrink uniformly into a point.

The following code is a more involved example visualizing the exaggerated displacement of atoms relative to 10 snapshots.

viz.layers.vectors.enable() viz.layers.vectors.style = "arrow" viz.layers.vectors.color = Color.HSV(1.0/3.0,1,1) viz.layers.vectors.vector = ( "@+10,xu - @xu", "@+10,yu - @yu", "@+10,zu - @zu" ) viz.layers.vectors.scale = 10.0 viz.layers.vectors.arrowWidth = 0.1 viz.layers.vectors.arrowHeadLength = 0.3 viz.layers.vectors.arrowHeadAngle = 45 viz.layers.vectors.flip = False

Trajectories layer

Not implemented.

Position transformation

The GelViz class has an internal definition of what to use for positioning objects in the 3D scene. This definition is stored in the class variable GelViz.POSITION, which is a tuple of 3 GelViz expressions which are evaluated to retrieve a coordinate for the atom.

The default value is ("@x","@y","@z"). If we shear in the x direction at a rate of 0.01 per snapshot, we can correct for the affine displacement by defining:

viz = GelViz() GelViz.POSITION = ("@xu - @yu*0.01*sn", "@yu", "@zu")

This definition should come immediately after creating a GelViz instance. Internally, the original data is kept intact: this is only a visual correction.

Important: This only affects the position of the atoms in the visualization, and consequently, the location of bonds and vectors, but it does not affect the computation of bonds or the direction/length of vectors. Apply the same correction in the definition of the vectors if necessary, and set the wrapped setting on the bonds layer to False if a position transformation is applied.

Using GelViz

Once a visualization script has been setup for GelViz, it is only a matter of using the script with the appropriate data file.

Keyboard controls

GelViz does not provide a graphical user interface. Instead, users are expected to interact using simple keyboard commands and the mouse.

The following are the available keyboard controls in GelViz. All features are viewport-specific, meaning that multiple viewports (if used) can operate independently.

1

Toggle atoms layer.

2

Toggle bonds layer.

3

Toggle vectors layer.

4

Toggle trajectories layer.

B

Toggle bounding box.

F

Toggle fog (depth cuing).

LEFT/RIGHT

Move to previous/next snapshot.

Ctrl+LEFT/RIGHT

Move to previous/next 10th snapshot.

Ctrl+S

Save screenshot to the screenshots directory.

Ctrl+A

Save GelViz animation as a series of PNG frames to the 'frames' directory.

P

Save POV-Ray file for current snapshot.
See POV-Ray output for more details.

CTRL+P

Save POV-Ray file for all snapshots (for animation).
See POV-Ray output for more details.

Numpad +

Increase fog distance

Numpad -

Decrease fog distance

C

Print current camera to console. This can be useful to reuse the location at a later time.

Also useful if you wish to change the view point in a previously generated POV-Ray file.

Mouse interactivity

The mouse can be used to move the camera around the data being visualized. To perform a mouse action, click with a mouse button and drag around the viewport.

Clicking and dragging (up/down or left/right) with the LEFT mouse button rotates the camera (or rather, the scene).

Clicking and dragging (up/down or left/right) with the MIDDLE mouse button (or mouse wheel) pans the camera left/right or up/down.

Clicking and dragging (up or down) with the RIGHT mouse button moves the camera closer or further to the scene, zooming in.

Automation

GelViz visualization scripts may also automate the manual functions above.

For instance, if viz is a GelViz instance and viewport one of its viewports, then the following command can be used to automatically save an animation without the need for user input.

viz.saveAnimation(viewport)

A list of snapshots can also be specified, such as in saveAnimation(viewport, [0,2,4,6]).

Using savePOV instead of saveAnimation, one or more snapshots may be saved as well.

See the GelViz class for the internal implementation as a guide for automation.

Cache files

In order to improve performance, GelViz creates several cache files during its normal use. These are stored in the same location as the dataset file, and have the same filename other than a suffix.

Files ending in .gvcache store GZip compressed representations of the dataset in memory. These files are generated when all the snapshots of a dataset have been stored in memory. Any further attempt to load all snapshots would instead import this file straight into memory, for a significant performance gain. Cache files are not created if only one snapshot is accessed or referenced.

In addition, when defining and using composite attributes, the value of these attributes for the whole dataset may be computed. This can take a significant amount of time. To speed up reuse, GelViz automatically creates a .gvdata file. The filename also contains a signature of 6 alphanumeric characters preceding this suffix. This signature is generated from the collection of the expression for the composite attributes.

In other words, adding a new composite attribute or modifying an existing one require the creation of a different file with data, as the collection of attribute data is now different. This system guarantees the data being loaded from the cache is an accurate representation of the attributes defined.

POV-Ray output

GelViz can export its visualizations as a POV-Ray file. The file is generated with several default settings that easily allow for customization of the final render. GelViz supports exporting single snapshots as well as animations for POV-Ray rendering.

About POV-Ray

POV-Ray is a high-quality raytracer available in multiple platforms. It is free and open source software.

Contrary to most 3D renderers, POV-Ray works by parsing a source code in its Scene Description Language (SDL), a human-readable text that describes a 3D scene to be rendered. The source code is then rendered (or "compiled") into a binary file, which is the final image. This design makes POV-Ray incredible versatile in conjunction with other programs.

Here's a simple example of POV-Ray code and the resulting scene.

#version 3.7 // Whitespace, new lines and order of statements are unimportant global_settings { assumed_gamma 1.0 // gamma value used for color computations } light_source { <3000, 3000, -3000> color 1 } // point light source camera { perspective angle 75 location <0.0 , 1.5 ,-4.0> right x*image_width/image_height look_at <0.0, 1.0, 0.0> } sky_sphere { // defines a spherical surface for the sky pigment { gradient <0,1,0> color_map { [0.00 rgb <0.6,0.7,1.0>] [0.35 rgb <0.1,0.0,0.8>] [0.65 rgb <0.1,0.0,0.8>] [1.00 rgb <0.6,0.7,1.0>] } scale 2 } } plane { // an infinite plane y, 0 // vertical vector, distance from origin texture{ pigment { hexagon color <1,1,1>*0.5 color <1,1,1>*0.75 color <1,1,1>*0.9 } } } sphere { // a shiny sphere <0,1,0>, 1.00 pigment{ color <1,0.7,0> } finish { phong 1 reflection { 0.40 metallic 0.3 } } }

Colors and vectors in POV-Ray are expressed using the angle brackets notation <>. Inside the brackets, a list of numbers is included separated by comma. Vectors can be multiplied by scalars.

Colors can be of several types, such as rgb (red, green, blue), rgbt (red, green, blue, transmit), rgbf (red, green, blue, filter), and rgbft (red, green, blue, filter, transmit). The transmit parameter acts similarly to an inverted alpha channel. A transmit value of 0.3 lets 30% of the background to be visible through the object. Filter works similarly, but it tints the transmitted based on the object's color. A color that both filters and transmit can be specified.

The components of a color can be any real number, with varying results. Usually, values ranging from 0 to 1 are sufficient, but a color vector such as <5,0,0> does represent a higher intensity red than <1,0,0>. This can be used to increase the brightness of objects. In addition, one can specify a single number in place of a color vector as a shorthand for the equivalent gray color, that is, 0.5 instead of <0.5, 0.5, 0.5>.

Position vectors operate similarly, but are limited to three components.

Objects can have a finish statement, which can be used to specify the specularity of reflections at the surface of the object, among other properties.

Exporting to POV-Ray

To export a visualization in POV-Ray format, simply press P. A folder named povray/[dataset filename] will be created in the visualization's directory, which will contain at least two POV-Ray files. Here, [dataset filename] is the name of the file imported into GelViz to visualize (e.g.: "test_gel" for "test_gel.lammpstrj").

The file [dataset filename].pov is the main POV-Ray file to be rendered. Additional files, named [dataset filename].#####.pov where ##### is a zero-padded number, contain the description of each snapshot (the bonds, atoms, vectors, etc). These files are separated so as to improve the performance of POV-Ray. The main POV-Ray file automatically includes the appropriate scene when rendering an animation.

Rendering in POV-Ray

To render the POV-Ray file, run the source code through POV-Ray via the following command line:

Windows

pvengine.exe /RENDER "path to filename" [rendering options]

UNIX/Linux/MacOS

povray -i"path to filename" [rendering options]

On Windows, POV-Ray includes an editor interface where source files may be loaded. Via this interface, rendering options can be instead entered at the text box above the editor area. Rendering can be initiated (or stopped) via the Alt+G keyboard shortcut.

For a full list of rendering options, see POV-Ray's documentation on section "3.2 Command-Line and INI-File Options". The following examples cover usage of the most relevant ones.

-o[name]

Optionally define an output file name.

+w400 +h300

Defines the image width to 400 pixels and height to 300 pixels.

+a0.2

Enables anti-aliasing (smooth edges) with threshold of 0.2. Improves rendering quality. Smaller is better, but slower.

+a0.2 +am2 +r4 -j

Enables 4-level (+r4) recursive (+am2) anti-aliasing with no jitter (-j, gives better results with smooth colors and is deterministic). Very high quality, but also slower. Useful if scene contains very thin objects.

+sp32 +ep4

Renders low-resolution previews of the scene progressively, starting from squares 32x32 in size and halving all the way to 4x4. This increases total rendering time (up to 25% if +ep1), but it's helpful for previewing longer renderings if total time is no constraint and one wishes to see what things will look like earlier.

+fn +ua

Set output to PNG format (+fn, but not necessary for POV-Ray 3.7+) and enables use of alpha channel (+ua) in the final image. This option makes the background transparent.

+rp3

Renders from the center of the image outwards, instead of from left-to-right, top-to-bottom order.

+qn

Set raytracing rendering quality to n, which is an integer from 0 (worst) to 9 (best). Useful for previewing.

+kfi0 +kf100

Render animation with 101 frames (from 0 to 100, inclusive). During animation, the internal variable clock will linearly interpolate from 0 to 1 for each frame. GelViz will export the proper command to use for an exported animation, and the file is structured so as to automatically handle it.

A typical usage would then be:

povray -i"~/animation.pov" -o"video" +w1024 +h768 +a0.02 +am2 +r4 -j +kfi0 +kff101

Visualization settings from GelViz

To maximize flexibility, the POV-Ray source code generated by GelViz includes several declarations that simplify customization of the visualization directly from POV-Ray. This allows for GelViz to be used as a real-time, interactive preview of POV-Ray's final output. Only visualization layers enabled and set up in GelViz will be exported.

Declarations start with the keyword #declare, and were named in a way to be self-evident. The following is a detailed explanation of the less obvious ones.

As an example, the default settings give the following result. It is recommended that you open this image in a new tab (middle click, or click with the mouse wheel) and open example images in separate tabs so comparisons can be made.

BACKGROUND

Defines the background color and fog color for the scene.

USE_FOG

Boolean specifying if fog should be used in the scene. Fog greatly increases the illusion of depth, and give a powerful visual cue for three dimensional information. As such, it is enabled by default. Below, fog was disabled.

SHADOWS

Boolean specifying if shadows should be computed. By default, this is set to false. The reason for this is that shadows create too much visual noise to be useful in a qualitative visualization. See the example below.

LIGHT_INTENSITY

By default, GelViz places two white point light sources opposite to each other to illuminate the POV-Ray scene. This helps getting rid of hard shadows. This declaration specifies the intensity of these lights.

AMBIENT_LIGHT

Ambient light models the illumination resulting from light coming from distant scattering sources. The effect is illumination in regions that are shadowed from light sources, which in turn means softer shadows. This declaration specifies the intensity of the ambient light in the scene.

BOX_FINISH

Specifies the finish declaration to use for the bounding box object.

BOX_BRIGHTNESS

Specifies a multiplier for the color of the bounding box, which increases its brightness.

ATOM_RADIUS

If the atoms layer was set to be visualized as points instead of spheres in GelViz, these are translated to POV-Ray as spheres with this global radius.

ATOM_RADIUS_MULTIPLIER

Multiplier for all atom radii. If atoms were set to be visualized as spheres of different radii, this multiplier allows a global, proportional increase in size.

ATOM_BRIGHTNESS

Specifies a multiplier for the color of the atoms, which changes their brightness.

ATOM_FINISH

Specifies the finish declaration to use for the atoms. For example, specular 1.

BOND_RADIUS

If the bonds layer was set to be visualized as lines instead of tubes in GelViz, these are translated to POV-Ray as cylinders with this global radius.

BOND_CAPS

Specifies whether the tubes representing bonds are to be closed or open. See bonds layer section for clearer examples.

BOND_ROUNDED

To create a more aesthetically pleasing and natural look for the bonds, GelViz adds a sphere at the end of the tubes in such a way that the tube is mutually tangent to the spheres at both ends. This declaration controls the inclusion of such spheres. See bonds layer section for clearer examples.

Note: this is, in principle, equivalent to visualizing the atoms with the same visual properties as the bonds. GelViz gives the freedom to visualize atoms in different and independent ways, so the two features are uncoupled.

BOND_ROUNDED_SCALE

Controls the scale of the spheres added by BOND_ROUNDED. This can be used to create bulges at the joints, simulating atoms with the same visual style as the bonds.

BOND_BRIGHTNESS

Specifies a multiplier for the color of the bonds, which changes their brightness.

BOND_FINISH

Specifies the finish declaration to use for the bonds. For example, specular 0.5.

VECTOR_BRIGHTNESS

Specifies a multiplier for the color of the vectors, which changes their brightness.

VECTOR_FINISH

Specifies the finish declaration to use for the vectors. For example, specular 1.

In addition, all colors should be specified in RGB+Transmit format. Transmit is roughly equivalent to 1.0 - alpha.

Additional features

POV-Ray has a wide range of advanced features that can be used to improve a visualization, but these are hard to automate and require manual tweaking with a lot of trial and error. This is by the very nature of the task and the tools at hand.

To give users a head start, here are a few features that might be worth using.

Focal blur

It is possible to enhance the sense of scale in a visualization by the inclusion of a focal blur. This effect makes objects that are too close or too far from the camera's focal distance to appear blurred. It occurs naturally in human vision, so the human brain naturally uses it perceive depth and scale.

The following code is a modified camera statement from the default used by GelViz that includes extra settings for producing this focal blur effect, albeit exaggerated. The FOCAL_POINT vector defines the point that should be in focus. A bright red sphere is used as a helper for seeing this location, and can be removed or commented out after a position is picked. It is recommended to use a larger variance and a smaller number for blur_samples for the initial test renders.

#declare FOCAL_POINT = <1,1,10>; sphere { FOCAL_POINT, 0.2 pigment { color rgb <10,0,0> } } camera { perspective location CAMERA_LOCATION look_at CAMERA_LOCATION+CAMERA_DIRECTION right -x*image_width/image_height // negative makes it a right-handed coordinate system, as in OpenGL angle CAMERA_FOV*CAMERA_ASPECT_RATIO // FOV is vertical in OpenGL, horizontal in POV-Ray // depth of field aperture 0.5 // [0,infinity] larger is narrower depth of field (blurrier) blur_samples 100 // number of rays per pixel for sampling focal_point FOCAL_POINT // point that is in focus confidence 0.95 // [0,1] when to move on while sampling (smaller is less accurate) variance 1/200 // [0,1] how precise to calculate (smaller is more accurate, less grainy) }

A more subtle use of this feature than shown here would be the ideal.

Camera motion

It might be useful to create animations where the camera moves or the scene rotates. POV-Ray supports exporting a sequence of images as animation frames, and files exported from GelViz make use of this feature to switch the snapshot being rendered in POV-Ray. In other words, the default animation is merely changing the snapshot being viewed.

To stop this behavior, first we must define SCENE_NUMBER to a fixed snapshot number.

#declare SCENE_NUMBER = 0;

Then, we can create an animation by using the variable clock in the POV-Ray source code. By default, clock will start at 0 and end at 1 at the end of the animation. A rotation animation can be made with the following code.

#declare CAMERA_RY = 360*clock;

Finally, to render the frames, POV-Ray must be invoked with the options +kff100 +kc. Here, 100 is the desired number of frames in the animation, and can be any other number. +kc indicates a cyclic animation, so that the last frame and the first frame follow naturally from one to the other. The result is shown below.

Another type of camera motion is translational. It is not trivial to move the camera around a 3D scene in a natural way, but a simple approximation can be made with interpolation. The following code uses a simple linear interpolation to control the camera.

#declare T = clock; #declare CAMERA_FROM = <0.000,0.000,70>; #declare CAMERA_TO = <10.000,15.000,10>; #declare CAMERA_ROTATION_FROM = <0,0>; #declare CAMERA_ROTATION_TO = <45,-30>; #declare CAMERA_LOOK_FROM = <0,0,0>; #declare CAMERA_LOOK_TO = <-10,5,-10>; #declare CAMERA_LOCATION = CAMERA_FROM + T*(CAMERA_TO-CAMERA_FROM); #declare CAMERA_RX = CAMERA_ROTATION_FROM.x + T*(CAMERA_ROTATION_TO.x-CAMERA_ROTATION_FROM.x); #declare CAMERA_RY = CAMERA_ROTATION_FROM.y + T*(CAMERA_ROTATION_TO.y-CAMERA_ROTATION_FROM.y); #declare CAMERA_LOOK_AT = CAMERA_LOOK_FROM + T*(CAMERA_LOOK_TO-CAMERA_LOOK_FROM); #declare CAMERA_ASPECT_RATIO = image_width/image_height; // aspect ratio for the image #declare CAMERA_FOV = 45.000; // field of view camera { perspective location CAMERA_LOCATION look_at CAMERA_LOOK_AT // dynamic looking direction - the default is to always face +z right -x*image_width/image_height // negative makes it a right-handed coordinate system, as in OpenGL angle CAMERA_FOV*CAMERA_ASPECT_RATIO // FOV is vertical in OpenGL, horizontal in POV-Ray }

The result is shown below, on the left. To improve upon this, we can simply tweak the value of T so it is more than a simple linear interpolation. What we are looking for is a function whose derivative is zero when clock = 0 or clock = 1.

Using a cosine function, for instance, we can write T differently, giving it an acceleration at the beginning followed by a slower deceleration at the end. This function is shown on the right (position as a function of time), compared to the simple identity (linear) function. The result is a much more natural movement, which is shown below on the right. Both animations have exactly the same duration and number of frames.

#declare T = 1-pow(cos(clock*pi/2),4);

By creating several such animations and changing the starting and final frame numbers in POV-Ray (+kfi and +kff), more complicated animations may be created.

Generating panoramas

POV-Ray supports several types of cameras. Using a spherical camera and a 2:1 ratio for the final image, the resulting output is compatible with several panorama viewers. This allows an interactive view of the gel "from inside".

camera { spherical location CAMERA_LOCATION look_at CAMERA_LOCATION+CAMERA_DIRECTION angle 360 180 }

Exporting movies

As shown in previous sections, with GelViz (and POV-Ray), a sequence of PNG images can be created for an animation. To assemble these frames into a video file, several programs can be used.

One possible choice is the popular ffmpeg video manipulation tool, a free and open source command-line application available in all platforms. A typical usage in this case would be:

ffmpeg -i frame%03d.png -c:v libx264 -r 15 -pix_fmt yuv420p video.mp4

This command looks for image frames from frame000.png to frame999.png (note the %03d placeholder for the number) and assembles whatever it finds, in order, into the video file video.mp4. The video playback rate will be 15 frames per second (-r 15). Colors will be converted to the standard (and more commonly supported for videos) YUV420 format, and the video will be compressed using the MPEG-4 video codec x264 (-c:v libx264) in the standard compression profile.

For further details, see the ffmpeg documentation.