Commit 78fc9128 authored by Alexander Stukowski's avatar Alexander Stukowski

Added the DataSeriesExporter class. Reworked the class interface of...

Added the DataSeriesExporter class. Reworked the class interface of FileExporter. Introduced the AsyncOperation class.
parent 36bf8e4a
......@@ -15,5 +15,4 @@ data = ase_to_ovito(ase_atoms)
# We may now create a Pipeline object with a StaticSource and use the
# converted dataset as input for a data pipeline:
pipeline = Pipeline(source = StaticSource())
pipeline.source.assign(data)
\ No newline at end of file
pipeline = Pipeline(source = StaticSource(data = data))
\ No newline at end of file
......@@ -11,11 +11,11 @@ pipeline.modifiers.append(ComputePropertyModifier(operate_on='bonds', output_pro
# Obtain pipeline results.
data = pipeline.compute()
positions = data.particles['Position'] # array with atomic positions
bond_topology = data.bonds['Topology'] # array with bond topology
bond_lengths = data.bonds['Length'] # array with bond lengths
bond_topology = data.particles.bonds['Topology'] # array with bond topology
bond_lengths = data.particles.bonds['Length'] # array with bond lengths
# Create bonds enumerator object.
bonds_enum = BondsEnumerator(data.bonds)
bonds_enum = BondsEnumerator(data.particles.bonds)
# Loop over atoms.
for particle_index in range(data.particles.count):
......@@ -25,7 +25,7 @@ for particle_index in range(data.particles.count):
a = bond_topology[bond_index, 0]
b = bond_topology[bond_index, 1]
# Bond orientations can be arbitrary (a->b or b->a):
# Bond directions can be arbitrary (a->b or b->a):
assert(a == particle_index or b == particle_index)
# Obtain the length of the bond from the 'Length' bond property:
......
from ovito.modifiers import CalculateDisplacementsModifier
mod = CalculateDisplacementsModifier()
mod.vis.enabled = True
mod.vis.color = (0,0,0)
\ No newline at end of file
############## Snippet code starts here ###################
modifier = CalculateDisplacementsModifier()
modifier.vis.enabled = True
modifier.vis.color = (0.8, 0.0, 0.5)
\ No newline at end of file
......@@ -3,6 +3,7 @@ pipeline = import_file("input/simulation.dump")
from ovito.modifiers import ColorCodingModifier
################# Code snippet begins here #####################
pipeline.modifiers.append(ColorCodingModifier(
property = 'Potential Energy',
gradient = ColorCodingModifier.Hot()
......
......@@ -5,7 +5,7 @@ from PyQt5.QtCore import Qt
# Prepare a data pipeline containing a ColorCodingModifier:
pipeline = import_file("input/simulation.dump")
color_mod = ColorCodingModifier(particle_property = 'peatom')
color_mod = ColorCodingModifier(property = 'peatom')
pipeline.modifiers.append(color_mod)
pipeline.add_to_scene()
......@@ -19,6 +19,6 @@ overlay = ColorLegendOverlay(
font_size = 0.12,
format_string = '%.2f eV')
# Attach the overlay to a Viewport that is going to be used for rendering:
# Attach the overlay to a Viewport, which is going to be used for image rendering:
viewport = Viewport(type = Viewport.Type.Top)
viewport.overlays.append(overlay)
\ No newline at end of file
import sys
if "ovito.modifiers.crystalanalysis" not in sys.modules: sys.exit()
############## Code snippet begins here ################
from ovito.io import import_file
from ovito.data import SurfaceMesh, SimulationCell
from ovito.modifiers import ConstructSurfaceModifier
......@@ -9,14 +9,13 @@ from ovito.modifiers import ConstructSurfaceModifier
pipeline = import_file("input/simulation.dump")
pipeline.modifiers.append(ConstructSurfaceModifier(radius = 2.9))
data = pipeline.compute()
mesh = data.expect(SurfaceMesh)
cell = data.expect(SimulationCell)
mesh = data.surfaces['surface']
# Query computed surface properties:
print("Surface area: %f" % data.attributes['ConstructSurfaceMesh.surface_area'])
print("Solid volume: %f" % data.attributes['ConstructSurfaceMesh.solid_volume'])
fraction = data.attributes['ConstructSurfaceMesh.solid_volume'] / cell.volume
fraction = data.attributes['ConstructSurfaceMesh.solid_volume'] / data.cell.volume
print("Solid volume fraction: %f" % fraction)
# Export the surface triangle mesh to a VTK file.
mesh.export_vtk('output/surface.vtk', cell)
mesh.export_vtk('output/surface.vtk', data.cell)
......@@ -27,6 +27,6 @@ pipeline.modifiers.append(PythonScriptModifier(function = modify))
# Export calculated MSD value to a text file and let OVITO's data pipeline do the rest:
export_file(pipeline, "output/msd_data.txt",
format = "txt",
format = "txt/attr",
columns = ["Timestep", "MSD"],
multiple_frames = True)
......@@ -4,6 +4,8 @@ from ovito.modifiers import PythonScriptModifier
# Load input data and create a data pipeline.
pipeline = import_file("input/simulation.dump")
################## Code snippet begins here #####################
from ovito.data import NearestNeighborFinder
import numpy as np
......@@ -30,27 +32,30 @@ reference_vectors *= lattice_parameter
# The number of neighbors to take into account per atom:
num_neighbors = len(reference_vectors)
################## Code snippet ends here #####################
def modify(frame, input, output):
################## Code snippet begins here ###################
def modify(frame, data):
# Show a status text in the status bar:
yield 'Calculating order parameters'
# Create output particle property.
order_params = output.particles.create_property(
order_params = data.particles_.create_property(
'Order Parameter', dtype=float, components=1)
# Prepare neighbor lists.
neigh_finder = NearestNeighborFinder(num_neighbors, input)
neigh_finder = NearestNeighborFinder(num_neighbors, data)
# Request write access to the output property array.
with order_params:
# Loop over all particles.
for i in range(len(order_params)):
for i in range(data.particles.count):
# Update progress indicator in the status bar
yield (i/len(order_params))
yield (i/data.particles.count)
# Stores the order parameter of the current atom
oparam = 0.0
......@@ -68,6 +73,7 @@ def modify(frame, input, output):
# Store result in output array.
order_params[i] = oparam / num_neighbors
################## Code snippet ends here #####################
pipeline.modifiers.append(PythonScriptModifier(function = modify))
pipeline.modifiers.append(modify)
pipeline.compute()
test_flag = False
# >>>>>>>>> snippet start here >>>>>>>>>>>>>>>
from ovito.modifiers import HistogramModifier
import matplotlib
matplotlib.use('Agg') # Activate 'agg' backend for off-screen plotting.
import matplotlib.pyplot as plt
import PyQt5.QtGui
def render(args):
# Look up the HistogramModifier in the pipeline whose data we will plot.
hist_modifier = None
for mod in args.scene.pipelines[0].modifiers:
if isinstance(mod, HistogramModifier):
hist_modifier = mod
break
if not hist_modifier: raise RuntimeError('Histogram modifier is not present')
# Request the output data collection from the current pipeline:
data = args.scene.selected_pipeline.compute()
# Look up the DataSeries object generated by the CoordinationAnalysisModifier in the pipeline output.
if 'coordination-rdf' not in data.series:
raise RuntimeError('No RDF data found')
rdf_data = data.series['coordination-rdf'].as_table()
# Compute plot size in inches (DPI determines label size)
dpi = 80
......@@ -26,8 +24,8 @@ def render(args):
fig.patch.set_alpha(0.5)
plt.title('Coordination')
# Plot histogram data
ax.bar(hist_modifier.histogram[:,0], hist_modifier.histogram[:,1])
# Plot RDF histogram data
ax.bar(rdf_data[:,0], rdf_data[:,1])
plt.tight_layout()
# Render figure to an in-memory buffer.
......@@ -47,10 +45,11 @@ def render(args):
from ovito.vis import PythonViewportOverlay, Viewport, TachyonRenderer
from ovito.io import import_file
from ovito.modifiers import CoordinationAnalysisModifier
pipeline = import_file("input/simulation.dump")
pipeline.add_to_scene()
pipeline.modifiers.append(HistogramModifier(bin_count=10, property='peatom'))
pipeline.modifiers.append(CoordinationAnalysisModifier(cutoff = 4.0))
viewport = Viewport(type = Viewport.Type.Top)
viewport.overlays.append(PythonViewportOverlay(function = render))
viewport.render_image(filename='output/simulation.png',
......
from ovito.io import import_file
from ovito.modifiers import PythonScriptModifier
# Load some input data:
pipeline = import_file("input/simulation.dump")
# Define our custom modifier function, which assigns a uniform color
# to all particles, similar to the built-in AssignColorModifier.
def assign_color(frame, input, output):
color_property = output.particles.create_property('Color')
def assign_color(frame, data):
color_property = data.particles_.create_property('Color')
with color_property:
color_property[:] = (0.2, 0.5, 1.0)
# Insert custom modifier into the data pipeline.
pipeline.modifiers.append(PythonScriptModifier(function = assign_color))
# Insert user-defined modifier function into the data pipeline.
# This implicitly creates an instance of the PythonScriptModifier class
# wrapping the Python function.
pipeline.modifiers.append(assign_color)
# Evaluate data pipeline. This will result in a call to assign_color() from above.
# Evaluate data pipeline. This will make the system invoke assign_color() defined above.
data = pipeline.compute()
print(data.particles['Color'][...])
\ No newline at end of file
......@@ -42,19 +42,19 @@ def quaternions_to_colors(qs):
return rs
def modify(frame, input, output):
def modify(frame, data):
""" The user-defined modifier function """
# Input:
orientations = input.particles['Orientation']
orientations = data.particles['Orientation']
# Output:
output.particles.create_property('Color', data=quaternions_to_colors(orientations))
data.particles_.create_property('Color', data=quaternions_to_colors(orientations))
################# Code snippet ends here ##################
# The following is for automated testing only and not shown in the documentation:
from ovito.io import import_file
from ovito.modifiers import PythonScriptModifier, PolyhedralTemplateMatchingModifier
from ovito.modifiers import PolyhedralTemplateMatchingModifier
pipeline = import_file("input/simulation.dump")
pipeline.modifiers.append(PolyhedralTemplateMatchingModifier(output_orientation = True))
pipeline.modifiers.append(PythonScriptModifier(function = modify))
pipeline.modifiers.append(modify)
pipeline.compute()
\ No newline at end of file
......@@ -11,25 +11,25 @@ from ovito.data import CutoffNeighborFinder
overlap_distance = 2.5
# The user-defined modifier function:
def modify(frame, input, output):
def modify(frame, data):
# Show this text in the status bar while the modifier function executes:
yield "Selecting overlapping particles"
# Create 'Selection' output particle property
selection = output.particles.create_property('Selection')
selection = data.particles_.create_property('Selection')
# Prepare neighbor finder
finder = CutoffNeighborFinder(overlap_distance, input)
finder = CutoffNeighborFinder(overlap_distance, data)
# Request write access to the output property array
with selection:
# Iterate over all particles
for index in range(len(selection)):
for index in range(data.particles.count):
# Progress bar update
yield (index / len(selection))
# Update progress display in the status bar.
yield (index / data.particles.count)
# Iterate over all closeby particles around the current center particle
for neigh in finder.find(index):
......@@ -42,6 +42,6 @@ def modify(frame, input, output):
break
# <<<<<<<<<<<<< snippet ends here <<<<<<<<<<<<<
pipeline.modifiers.append(PythonScriptModifier(function = modify))
pipeline.modifiers.append(modify)
data = pipeline.compute()
assert('Selection' in data.particles.keys())
assert('Selection' in data.particles)
from ovito.io import import_file
from ovito.data import SimulationCell
pipeline = import_file("input/simulation.dump")
cell = pipeline.compute().expect(SimulationCell)
# Print cell matrix to the console. [...] is for casting to Numpy.
data = pipeline.compute()
cell = data.cell
# Print cell matrix to the console. [...] is for casting to Numpy array.
print(cell[...])
import numpy.linalg
# Compute simulation cell volume by taking the determinant of the
# Compute simulation box volume by taking the determinant of the
# left 3x3 submatrix of the cell matrix:
vol = abs(numpy.linalg.det(cell[0:3,0:3]))
# The SimulationCell.volume property computes the same value.
# The SimulationCell.volume property yields the same value.
assert numpy.isclose(cell.volume, vol)
# Make cell twice as large along the Y direction by scaling the second cell vector:
with cell:
cell[:,1] *= 2.0
with data.cell_:
data.cell_[:,1] *= 2.0
# Change display color of simulation cell to red:
cell.vis.rendering_color = (1.0, 0.0, 0.0)
......@@ -43,7 +43,7 @@ pipeline.modifiers.append(PythonScriptModifier(function = modify))
# Let OVITO do the computation and export the number of identified
# antisites as a function of simulation time to a text file:
export_file(pipeline, "output/antisites.txt", "txt",
export_file(pipeline, "output/antisites.txt", "txt/attr",
columns = ['Timestep', 'Antisite_count'],
multiple_frames = True)
......
......@@ -168,13 +168,13 @@ It directly provides the vectors from the central atom to its nearest neighbors.
Let us start by defining some inputs for the order parameter calculation at the global scope:
.. literalinclude:: ../example_snippets/order_parameter_calculation.py
:lines: 7-32
:lines: 9-34
The actual modifier function needs to create an output particle property, which will store the calculated
order parameter of each atom. Two nested loops run over all input atoms and their 12 nearest neighbors respectively.
.. literalinclude:: ../example_snippets/order_parameter_calculation.py
:lines: 34-70
:lines: 39-75
Note that the ``yield`` statements in the modifier function above are only needed to support progress feedback in the
graphical version of OVITO and to give the pipeline system the possibility to interrupt the long-running calculation when needed.
......@@ -245,14 +245,14 @@ You can copy/paste the source code into the script input field and adjust the pa
Example O2: Including data plots in rendered images
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following script demonstrates how to use the `Matplotlib <http://matplotlib.org>`_ Python module to render a histogram
on top the three-dimensional visualization. The histogram data is dynamically computed by a :py:class:`~ovito.modifiers.HistogramModifier`
in the data pipeline in this example.
Ths user-defined viewport overlay function demonstrates how to use the `Matplotlib <http://matplotlib.org>`_ Python module to render the radial
distribution function, which is dynamically computed by a :py:class:`~ovito.modifiers.CoordinationAnalysisModifier`
in the data pipeline, on top the three-dimensional visualization.
.. image:: /../manual/images/viewport_overlays/python_script_plot_example.png
.. literalinclude:: ../example_snippets/overlay_data_plot.py
:lines: 4-42
:lines: 4-40
.. _example_highlight_particle_overlay:
......
......@@ -126,7 +126,7 @@ supports the ``txt`` output format::
modifier = ExpressionSelectionModifier(expression = "PotentialEnergy < -3.9")
pipeline.modifiers.append(modifier)
export_file(pipeline, "potenergy.txt", "txt", multiple_frames = True,
export_file(pipeline, "potenergy.txt", "txt/attr", multiple_frames = True,
columns = ["Frame", "SelectExpression.num_selected"])
The ``multiple_frames`` keyword arguments tells the :py:func:`~ovito.io.export_file` function to evaluate the pipeline for all
......
"""
This OVITO script writes the number of bonds at each timestep to a text file.
You should run this script from within the graphical user interface
using the Scripting->Run Script File menu option.
You should run this macro script from within the graphical user interface
using the "Run Script File" menu option.
"""
import ovito
from ovito.data import *
......@@ -10,7 +10,7 @@ from ovito.data import *
# Open output file for writing:
fout = open("outputfile.txt", "w")
for frame in range(ovito.dataset.anim.first_frame, dataset.anim.last_frame+1):
for frame in range(ovito.scene.anim.first_frame, dataset.anim.last_frame+1):
data = dataset.selected_node.compute(frame)
num_bonds = len(data.bonds.array) // 2 # Divide by 2 because each bond is represented by two half-bonds.
print(frame, num_bonds, file = fout)
......
......@@ -2,8 +2,8 @@
This OVITO script exports the dislocation lines extracted by the
DXA analysis modifier to a file.
You should run this script from within the graphical user interface
using the Scripting->Run Script File menu option.
You should run this macro script from within the graphical user interface
using the "Run Script File" menu function.
"""
import ovito
from ovito.data import *
......
......@@ -117,8 +117,8 @@ def export_node(node):
export_simulation_cell(group_name, data.cell)
# Loop over scene nodes.
for i in range(len(ovito.dataset.scene_nodes)):
export_node(ovito.dataset.scene_nodes[i])
for i in range(len(ovito.scene.scene_nodes)):
export_node(ovito.scene.scene_nodes[i])
# Write node resources.
......
......@@ -22,8 +22,10 @@
IF(OVITO_BUILD_GUI)
ADD_SUBDIRECTORY(qwt)
ENDIF()
IF(OVITO_BUILD_PLUGIN_PARTICLES)
IF(OVITO_BUILD_PLUGIN_STDOBJ OR OVITO_BUILD_PLUGIN_PARTICLES)
ADD_SUBDIRECTORY(muparser)
ENDIF()
IF(OVITO_BUILD_PLUGIN_PARTICLES)
ADD_SUBDIRECTORY(voro++)
ADD_SUBDIRECTORY(ptm)
ADD_SUBDIRECTORY(copr)
......
......@@ -55,9 +55,13 @@ namespace Ovito {
class PromiseWatcher;
class AsynchronousTaskBase;
class TrackingPromiseState;
class SynchronousPromiseState;
class AsyncOperation;
template<typename tuple_type> class DirectContinuationPromiseState;
template<typename... R> class Future;
template<typename... R> class SharedFuture;
template<typename... R> class Promise;
template<class BaseState, class tuple_type> class PromiseStateWithResultStorage;
using PromiseStatePtr = std::shared_ptr<PromiseState>;
OVITO_END_INLINE_NAMESPACE
OVITO_BEGIN_INLINE_NAMESPACE(Mesh)
......
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (2017) Alexander Stukowski
// Copyright (2018) Alexander Stukowski
//
// This file is part of OVITO (Open Visualization Tool).
//
......@@ -78,6 +78,14 @@ DataSet::~DataSet()
}
}
/******************************************************************************
* Returns the TaskManager responsible for this DataSet.
******************************************************************************/
TaskManager& DataSet::taskManager() const
{
return container()->taskManager();
}
/******************************************************************************
* Returns a viewport configuration that is used as template for new scenes.
******************************************************************************/
......@@ -242,7 +250,7 @@ SharedFuture<> DataSet::whenSceneReady()
}
if(!_sceneReadyFuture.isValid()) {
_sceneReadyPromise = Promise<>::createSynchronous(nullptr, true, false);
_sceneReadyPromise = SignalPromise::create(true);
_sceneReadyFuture = _sceneReadyPromise.future();
_sceneReadyTime = animationSettings()->time();
makeSceneReady(false);
......@@ -253,7 +261,7 @@ SharedFuture<> DataSet::whenSceneReady()
}
/******************************************************************************
* Makes sure all data pipeline have been evaluated.
* Requests the (re-)evaluation of all data pipelines in the current scene.
******************************************************************************/
void DataSet::makeSceneReady(bool forceReevaluation)
{
......@@ -371,7 +379,7 @@ void DataSet::pipelineEvaluationFinished()
* This is the high-level rendering function, which invokes the renderer to generate one or more
* output images of the scene. All rendering parameters are specified in the RenderSettings object.
******************************************************************************/
bool DataSet::renderScene(RenderSettings* settings, Viewport* viewport, FrameBuffer* frameBuffer, TaskManager& taskManager)
bool DataSet::renderScene(RenderSettings* settings, Viewport* viewport, FrameBuffer* frameBuffer, AsyncOperation&& operation)
{
OVITO_CHECK_OBJECT_POINTER(settings);
OVITO_CHECK_OBJECT_POINTER(viewport);
......@@ -381,8 +389,7 @@ bool DataSet::renderScene(RenderSettings* settings, Viewport* viewport, FrameBuf
SceneRenderer* renderer = settings->renderer();
if(!renderer) throwException(tr("No rendering engine has been selected."));
Promise<> renderTask = Promise<>::createSynchronous(&taskManager, true, true);
renderTask.setProgressText(tr("Initializing renderer"));
operation.setProgressText(tr("Initializing renderer"));
try {
// Resize output frame buffer.
......@@ -417,9 +424,14 @@ bool DataSet::renderScene(RenderSettings* settings, Viewport* viewport, FrameBuf
// Render a single frame.
TimePoint renderTime = animationSettings()->time();
int frameNumber = animationSettings()->timeToFrame(renderTime);
renderTask.setProgressText(QString());
if(!renderFrame(renderTime, frameNumber, settings, renderer, viewport, frameBuffer, videoEncoder, taskManager))
renderTask.cancel();
operation.setProgressText(tr("Rendering frame %1").arg(frameNumber));
renderFrame(renderTime, frameNumber, settings, renderer, viewport, frameBuffer, videoEncoder, std::move(operation));
}
else if(settings->renderingRangeType() == RenderSettings::CUSTOM_FRAME) {
// Render a specific frame.
TimePoint renderTime = animationSettings()->frameToTime(settings->customFrame());
operation.setProgressText(tr("Rendering frame %1").arg(settings->customFrame()));
renderFrame(renderTime, settings->customFrame(), settings, renderer, viewport, frameBuffer, videoEncoder, std::move(operation));
}
else if(settings->renderingRangeType() == RenderSettings::ANIMATION_INTERVAL || settings->renderingRangeType() == RenderSettings::CUSTOM_INTERVAL) {
// Render an animation interval.
......@@ -438,18 +450,17 @@ bool DataSet::renderScene(RenderSettings* settings, Viewport* viewport, FrameBuf
numberOfFrames = (numberOfFrames + settings->everyNthFrame() - 1) / settings->everyNthFrame();
if(numberOfFrames < 1)
throwException(tr("Invalid rendering range: Frame %1 to %2").arg(settings->customRangeStart()).arg(settings->customRangeEnd()));
renderTask.setProgressMaximum(numberOfFrames);
operation.setProgressMaximum(numberOfFrames);
// Render frames, one by one.
for(int frameIndex = 0; frameIndex < numberOfFrames; frameIndex++) {
int frameNumber = firstFrameNumber + frameIndex * settings->everyNthFrame() + settings->fileNumberBase();
renderTask.setProgressValue(frameIndex);
renderTask.setProgressText(tr("Rendering animation (frame %1 of %2)").arg(frameIndex+1).arg(numberOfFrames));
operation.setProgressValue(frameIndex);
operation.setProgressText(tr("Rendering animation (frame %1 of %2)").arg(frameIndex+1).arg(numberOfFrames));
if(!renderFrame(renderTime, frameNumber, settings, renderer, viewport, frameBuffer, videoEncoder, taskManager))
renderTask.cancel();
if(renderTask.isCanceled())
renderFrame(renderTime, frameNumber, settings, renderer, viewport, frameBuffer, videoEncoder, operation.createSubOperation());
if(operation.isCanceled())
break;
// Go to next animation frame.
......@@ -475,14 +486,14 @@ bool DataSet::renderScene(RenderSettings* settings, Viewport* viewport, FrameBuf
throw;
}
return !renderTask.isCanceled();
return !operation.isCanceled();
}
/******************************************************************************
* Renders a single frame and saves the output file.
******************************************************************************/
bool DataSet::renderFrame(TimePoint renderTime, int frameNumber, RenderSettings* settings, SceneRenderer* renderer, Viewport* viewport,
FrameBuffer* frameBuffer, VideoEncoder* videoEncoder, TaskManager& taskManager)
FrameBuffer* frameBuffer, VideoEncoder* videoEncoder, AsyncOperation&& operation)
{
// Determine output filename for this frame.
QString imageFilename;
......@@ -502,8 +513,6 @@ bool DataSet::renderFrame(TimePoint renderTime, int frameNumber, RenderSettings*
}
}
Promise<> renderTask = Promise<>::createSynchronous(&taskManager, true, true);
// Set up preliminary projection.
ViewProjectionParameters projParams = viewport->computeProjectionParameters(renderTime, settings->outputImageAspectRatio());
......@@ -516,8 +525,8 @@ bool DataSet::renderFrame(TimePoint renderTime, int frameNumber, RenderSettings*
}
// Request scene bounding box.
Box3 boundingBox = renderer->computeSceneBoundingBox(renderTime, projParams, nullptr, renderTask);
if(renderTask.isCanceled()) {
Box3 boundingBox = renderer->computeSceneBoundingBox(renderTime, projParams, nullptr, operation);
if(operation.