Difference between revisions of "ParaView/Python Scripting"
|Line 41:||Line 41:|
['AVSUCDReader', 'AVSUCDreader', 'ActiveObjects', 'AlltoN', 'AnnotateTime', 'AppendAttributes', 'AppendDatasets',
['AVSUCDReader', 'AVSUCDreader', 'ActiveObjects', 'AlltoN', 'AnnotateTime', 'AppendAttributes',
'AppendDatasets', 'AppendGeometry', 'Arrow', 'Axes', 'BYUreader', 'Balance', 'BlockScalars',
'BlockSelectionSource', 'Box', 'COSMOreader', 'CSVreader', 'CacheKeeper', 'Calculator', 'CellCenters',
'CellDatatoPointData', 'Clean', 'CleantoGrid', 'ClientServerMoveData', 'Clip',
'CompositeDataIDSelectionSource', 'ComputeDerivatives', 'Cone', 'Connect',
'Connectivity', 'Contour', ...
'Connectivity', 'Contour', ...
Revision as of 10:18, 21 May 2009
NOTE: This document if based on ParaView 3.6 or higher. If you are using 3.4, go to the history page and select the version from May 13, 2009.
This is a document in progress. It will be done soon.
- Link to online help for list of proxies and properties
- Talk about active pipeline objects
- Talk about getting list of pipeline objects
- Talk about deleting objects
- Talk about passing property value to the constructor
- Talk about SetProperties()
- 1 ParaView and Python
- 2 Quick Start - a Tutorial
- 3 paraview.simple Module
- 4 Proxies and Properties
ParaView and Python
ParaView offers rich scripting support through Python. This support is available as part of the ParaView client (paraview), an MPI-enabled batch application (pvbatch), the ParaView python client (pvpython) or any other Python-enabled application. Using Python, users and developers can gain access to the ParaView engine called Server Manager.
Note: Server Manager is a library that is designed to make it easy to build distributed client-server applications.
Quick Start - a Tutorial
To start interacting with the Server Manager, you have to load the "simple" module. This module can be loaded from any python interpreter as long as the necessary files are in PYTHONPATH. These files are the shared libraries located in the paraview binary directory and python modules in the paraview directory: paraview/simple.py, paraview/vtk.py etc. You can also use either pvpython (for stand-alone or client/server execution), pvbatch (for non-interactive, distributed batch processing) or the python shell invoked from Tools -> Python Shell using the ParaView client to execute Python scripts. You do not have to set PYTHONPATH when using these. In this tutorial, I will be using the python integrated development environment IDLE. My PYTHONPATH is set to the following.
This is on my Mac and using the build tree. In IDLE, let’s start by loading the servermanager module.
>>> from paraview.simple import *
Note: Importing the paraview module directly is deprecated although still possible for backwards compatibility. This document refers to the simple module alone.
In this example, we will use ParaView in the stand-alone mode. Connecting to a ParaView server running on a cluster is covered later in this document.
Creating a Pipeline
The simple module contains many functions to instantiate sources, filters and other related objects.. You can get a list of classes these modules contain by using dir() as shown in the following example.
>>> dir(paraview.simple) ['AVSUCDReader', 'AVSUCDreader', 'ActiveObjects', 'AlltoN', 'AnnotateTime', 'AppendAttributes', 'AppendDatasets', 'AppendGeometry', 'Arrow', 'Axes', 'BYUreader', 'Balance', 'BlockScalars', 'BlockSelectionSource', 'Box', 'COSMOreader', 'CSVreader', 'CacheKeeper', 'Calculator', 'CellCenters', 'CellDatatoPointData', 'Clean', 'CleantoGrid', 'ClientServerMoveData', 'Clip', 'CompositeDataIDSelectionSource', 'ComputeDerivatives', 'Cone', 'Connect', 'Connectivity', 'Contour', ...
Let’s start by creating a Cone object:
>>> cone = Cone()
The object assigned to cone is a proxy for the actual VTK object vtkConeSource. This proxy provides a set of properties and methods to control the behavior of the underlying VTK object(s). These objects may be in the same process (built-in server) or on one or more server processes (distributed remote server). The proxy will handle communication with the VTK objects in a transparent way. You can get some documentation about the proxy using help().
>>> help(cone) Help on Cone in module paraview.servermanager object: class Cone(SourceProxy) | The Cone source can be used to add a polygonal cone to the 3D scene. The output of the Cone source is polygonal data. | | Method resolution order: | Cone | SourceProxy | Proxy | __builtin__.object | | Methods defined here: | | Initialize = aInitialize(self, connection=None) | | ---------------------------------------------------------------------- | Data descriptors defined here: | | Capping | If this property is set to 1, the base of the cone will be capped with a filled polygon. Otherwise, the base of the cone will be open. | | Center | This property specifies the center of the cone. | | Direction | Set the orientation vector of the cone. The vector does not have to be normalized. The cone will point in the direction specified. | | Height | This property specifies the height of the cone. | | Radius | This property specifies the radius of the base of the cone. | | Resolution | This property indicates the number of divisions around the cone. The higher this number, the closer the polygonal approximation will come to representing a cone, and the more polygons it will contain. | |...
This gives you a full list of properties. Let’s check what the resolution property is set to.
>>> coneS.Resolution 6
You can increase the resolution as shown below.
>>> cone.Resolution = 32
Alternatively, we could have specified a value for resolution when creating the proxy.
>>> cone = Cone(Resolution=32)
You can assign values to any number of properties during construction using keyword arguments. Let’s also change the center.
>>> cone.Center [0.0, 0.0, 0.0] >>> cone.Center = [1, 2, 3]
Vector properties such as this one support setting and getting of individual elements as well as slices (ranges of elements).
>>> cone.Center[0:3] = [1, 2, 3] >>> cone.Center [1.0, 2.0, 3.0]
Next, let’s apply a shrink filter to the cone:
>>> shrinkFilter = Shrink(cone) >>> shrinkFilter.Input <paraview.servermanager.Cone object at 0xaf701f0>
At this point, if you are interested in getting some information about the output of the shrink filter, you can force it to update (which will also cause the execution of the cone source. For details about VTK pipeline model, see one of the VTK books.
>>> shrinkFilter.UpdatePipeline() >>> shrinkFilter.GetDataInformation().GetNumberOfCells() 33L >>> shrinkFilter.GetDataInformation().GetNumberOfPoints() 128L
We will cover the DataInformation class in more detail later.
Now that we created a small pipeline, let’s render the result. You will need two objects to render the output of an algorithm in a scene: a representation and a view. A representation is responsible for taking a data object and rendering it in a view. A view is responsible for managing a render context and a collection of representations.
>>> Show(ShrinkFilter) >>> Render()
In the example above, we assigned the value returned by Cone() and Shrink() to Python variables and used them to build our pipeline. ParaView keeps track of the last pipeline object created by the user. This allows us to accomplish everything we did above using the following code.
>>> from paraview.simple import * >>> Cone() <paraview.servermanager.Cone object at 0x2910f0> >>> SetProperties(Resolution=32) >>> Shrink() <paraview.servermanager.Shrink object at 0xaf64050> >>> Show() vtkConeSource : [ ........... ] vtkShrinkFilter : [ ........... ] <paraview.servermanager.UnstructuredGridRepresentation object at 0xaf57f90> >>> Render() vtkPVGeometryFilter : [ ........... ] vtkPVCacheKeeper : [ ........... ] vtkPainterPolyDataMapper : [ ........... ] vtkPainterPolyDataMapper : [ ........... ] <paraview.servermanager.RenderView object at 0xaf57ff0>
This was a quick introduction to the paraview.simple module. In the following sections, we will discuss the Python interface in more detail and introduce more advanced concepts.
The simple module is a ParaView component written using Python on top of the VTK Server Manager C++ library. Its purpose is to make it easier to create ParaView data analysis and visualization pipelines using Python. The simple module can be loaded from Python interpreters running in several applications.
- pvpython: The pvpython application, distributed with the ParaView application suite, is a Python client to the ParaView servers. It supports interactive execution as well as batch execution.
- pvbatch: The pvbatch application, also distributed with the ParaView application suite, is a Python application designed to run batch scripts on distributed servers. When ParaView is compiled with MPI,
- pvbatch can be launched as an MPI program. In this mode, the first node will load a Python script specified as a command-line argument and execute it using a special built-in connection on all nodes. This application does not support interactive execution.
- paraview: Python scripts can be run from the paraview client using the Python shell that is invoked from Tools -> Python Shell. The Python shell supports interactive mode as well as loading of scripts from file.
- External Python interpreter: Any Python-capable application can load the paraview.simple module if the right environment is configured. For this to work, you either have to install the paraview Python modules (including the right shared libraries) somewhere in sys.path or you have to set PYTHONPATH to point to the right locations.
The paraview.simple module contains several Python classes designed to be Python-friendly as well as all classes wrapped from the C++ Server Manager library. The most important classes are as follows.
- Property and sub-classes
We will cover these classes in detail in following sections.
Connecting to a Server
Unless you are using the Python shell that is in the paraview application or you are using ParaView in the stand-alone mode, the first step to any Server Manager Python script is connecting to a server. Note: you cannot connect to the ParaView application from a stand-alone Python shell. You can only connect to a server.
To connect to a server, use servermanager.Connect(). This method takes 4 arguments, all of which have default values.
def Connect(ds_host=None, ds_port=11111, rs_host=None, rs_port=11111)
When connecting to a server (pvserver), specify only the first 2 arguments. These are the server name (or IP address) and port number.
When connecting to a data-server/render-server pair, you have to specify all four arguments. The first 2 are the host name (or IP address) and port number of the data server, the last 2 those of the render server. Here are some examples.
# Connect to pvserver running on amber1 (first node of our test cluster) # using the default port 11111 >>> Connect(‘amber1’) # Connect to pvdataserver running on the amber cluster, pvrenderserver # running on Berk’s desktop >>> Connect(‘amber1’, 12000, ‘kamino’, 11111)
Note: Connect() will return None on failure. To be safe, you should check the return value of Connect().
You can access the documentation of all Proxy types by using Python's built-in help.
>>> help(paraview.simple.Cone) Help on function CreateObject in module paraview.simple: CreateObject(*input, **params) The Cone source can be used to add a polygonal cone to the 3D scene. The output of the Cone source is polygonal data.
To get the full documentation, you have to create an instance.
>>> c = Cone() >>> help(c)
This documentation is automatically generated from the Server Manager configuration files and is identical to the class documentation found under the paraview Help menu. Beyond this document and the online help, there are a few useful documentation sources.
- The ParaView Guide: http://www.kitware.com/products/paraviewguide.html
- The ParaView source documentation: http://www.paraview.org/doc/
- The ParaView Wiki: http://paraview.org/Wiki/ParaView
If you are interested in learning more about the Visualization Toolkit that is at the foundation of ParaView, visit http://vtk.org.
Proxies and Properties
The VTK Server Manager design uses the Proxy design pattern (See Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides for details). Quoting from Wikipedia: “A proxy, in its most general form, is a class functioning as an interface to another thing. The other thing could be anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate”. In the case of Server Manager, a Proxy object acts as a proxy to one or more VTK objects. Most of the time, these are server-side objects and are distributed to the server nodes. Proxy objects allow you to interact with these object as if you directly have access to them, manipulate them and obtain information about them. When creating visualization pipelines, you create proxies instead of VTK objects.
>>> sphereSource = vtk.vtkSphereSource() # VTK-Python script >>> sphereSourceP = Sphere() # ParaView script
A proxy also provides an interface to modify the properties of the objects it maintains. For example, instead of
>>> sphereSource.SetCenter(1.0, 1.0, 0.0)
you can write the following.
>>> sphere.Center = [1.0, 1.0, 0.0]
Property objects are used to read and modify the properties of VTK objects represented by proxies. Each proxy has a list of properties defined in the Server Manager configuration files. The property interface of the Server Manager library is somewhat cumbersome. Here is how you can set the radius property of a sphere source.
>>> rp = sphere.GetProperty("Radius") >>> rp.SetElement(0, 2) 1 >>> sphere.UpdateProperty("Radius")
The servermanager module makes property access much easier by defining Python property accessors for property objects:
>>> sphere.Radius = 3
Here Radius is a Python property which, when a value is assigned to it, calls sphere.SetPropertyWithName("Radius",3). The class hierarchy for property classes looks like this.
NEW IMAGE HERE
￼ All Property classes define the following methods.
Therefore, all of the following are supported.
>>> sphere.Center [0.0, 0.0, 0.0] >>> sphere.Center = 1 >>> sphere.Center[0:3] = [1,2,3] >>> sphere.Center[0:3] [1.0, 2.0, 3.0] >>> len(sphere.Center) 3
ProxyProperty and InputProperty also define
to support del() and append(), similar to Python list objects.
VectorProperty is used for scalars, vectors and lists of integer and floating point numbers as well as strings. Most properties of this type are simple. Examples include Sphere.Radius (double scalar), Sphere.Center (vector of doubles), a2DGlyph.Filled (boolean), a2DGlyph.GlyphType (enumeration), a3DText.Text (string) and Contour.Isosurfaces (list of doubles). Some properties may be more complicated because they map to C++ methods with mixed argument types. Two good examples of this case are Glyph.Scalars and ExodusIIReader.PointVariables.
>>> reader = ExodusIIReader(FileName='.../can.ex2') # These variables are currently selected >>> reader.PointVariables ['DISPL', 'VEL', 'ACCL'] # These are available in the file >>> reader.PointVariables.Available ['DISPL', 'VEL', 'ACCL'] # Enable the DISPL array only >>> reader.PointVariables = ['DISPL'] # Force read >>> reader.UpdatePipeline() # Now check the output. Note: GlobalNodeId is generated automatically by the reader. >>> reader.PointData[:] [Array: GlobalNodeId, Array: PedigreeNodeId, Array: DISPL]
This example demonstrates the use of ExodusIIReader.PointVariables. This is a VectorProperty that represents a list of array names. The underlying C++ function has a signature of SetPointResultArrayStatus( const char* name, int flag ). This method is usually called once per array to enable or disable it (i.e. to set whether the reader will read a particular array).
Glyph.Scalars is a bit more complicated. This property allows the developer to select the scalar array with which to scale the glyphs.
>>> sph = Sphere() >>> elev=Elevation(sph) # Glyph the points of the sphere with spheres >>> glyph=Glyph(elev, GlyphType='Sphere') # Scale the glyph with the Elevation array >>> glyph.Scalars = 'Elevation' >>> glyph.Scalars ['POINTS', 'Elevation'] # The above shows the association of the array as well as its name. # In this case, the array is associated with POINTS as it has to be # since Glyph cannot scale by cell arrays. We could have done: >>> glyph.Scalars = ['POINTS', 'Elevation'] # Enable scaling by scalars >>> glyph.ScaleMode = 'scalar'
Here the property Scalars maps to SetInputArrayToProcess( int idx, int port, int connection, int fieldAssociation, const char *name ) which has four integer arguments (some of which are enumeration) and 1 string argument (see vtkAlgorithm documentation for details).
Properties are either regular (push) or information (pull) properties. Information properties do not have a VTK method associated with them and are responsible for getting information from the server. A good example of an information property is TimestepValues which returns all time steps available in a file (if the reader supports time).
>>> reader = ExodusIIReader(FileName='.../can.ex2') >>> reader.TimestepValues [0.0, 0.00010007373930420727, 0.00019990510190837085, 0.00029996439116075635, 0.00040008654468692839, 0.00049991923151537776, 0.00059993512695655227, 0.00070004, ...]
You can obtain a list of properties a proxy supports by using help(). However, this does not allow introspection programmatically. If you need to obtain information about a proxy’s properties programmatically, you can use a property iterator:
>>> for prop in glyph: print type(prop), prop.GetXMLLabel() <class 'paraview.servermanager.InputProperty'> Input <class 'paraview.servermanager.VectorProperty'> Maximum Number of Points <class 'paraview.servermanager.VectorProperty'> Random Mode <class 'paraview.servermanager.ArraySelectionProperty'> Scalars <class 'paraview.servermanager.ArraySelectionProperty'> Vectors <class 'paraview.servermanager.VectorProperty'> Orient <class 'paraview.servermanager.VectorProperty'> Set Scale Factor <class 'paraview.servermanager.EnumerationProperty'> Scale Mode <class 'paraview.servermanager.InputProperty'> Glyph Type <class 'paraview.servermanager.VectorProperty'> Mask Points
The XMLLabel is the text display by the graphical user interface. Note that there is a direct mapping from the XMLLabel to the property name. If you remove all spaces from the label, you get the property name. You can use the PropertyIterator object directly.
>>> it = s.__iter__() >>> for i in it: print it.GetKey(), it.GetProperty()
The Server Manager provides information about values that are valid for properties. The main use of this information is for the user interface to provide good ranges and choices in enumeration. However, some of this information is also very useful for introspection. For example, enumeration properties look like simple integer properties unless a (value, name) pair is associated with them. The Server Manager uses Domain objects to store this information. The contents of domains may be loaded from xml configuration files or computed automatically. Let’s look at an example.
>>> s = Sphere() >>> ShowSphere(s) >>> dp = GetDisplayProperties(s) >>> dp.Representation 'Surface' # The current representation type is Surface. What other types # are available? >>> dp.GetProperty("Representation").Available ['Outline', 'Points', 'Wireframe', 'Surface', 'Surface With Edges'] # Choose outline >>> dp.Representation = 'Outline'
Source proxies are proxies that represent pipeline objects (For more information about VTK pipelines, see the VTK books: http://vtk.org/buy-books.php). They have special properties to connect them as well as special method to query the meta-data of their output. To connect a source proxy to another, use one of its input properties.
# Either >>> glyph = Glyph(elev) # or >>> glyph.Input = elev
The SourceProxy class provides several additional properties and methods that are specific to pipelines (See vtkSMSourceProxy documentation for a full list).
- UpdatePipelineInformation(): This method calls UpdateInformation() on the VTK algorithm. It also calls UpdatePropertyInformation() to update any information properties.
- UpdatePipeline(): This method calls Update() on the VTK algorithm causing a pipeline execution if the pipeline changed. Another way of causing pipeline updates is to render. The render view updates all connected pipelines.
- GetDataInformation(): This method is used to obtain meta-data about one output. It is discussed further below.
- PointData and CellData properties discussed below.
There are two common ways of getting meta-data information from a proxy: information properties and DataInformation. Information properties are updated automatically every time UpdatePropertyInformation() and UpdatePipelineInformation() are called. All you have to do is read the data from the property as usual. To get a DataInformation object from a source proxy use GetDataInformation(port=0). By default, this method returns data information for the first output. You can pass an optional port number to get information for another output. You can get detailed documentation on DataInformation by using help() and by reading online documentation for vtkPVDataInformation (http://www.paraview.org/doc/nightly/html/classvtkPVDataInformation.html). Here are the use of some common methods.
>>> di = glyph.GetDataInformation(0) >>> di <paraview.servermanager.DataInformation object at 0x2d0920d0> >>> glyph.UpdatePipeline() # Get the data type. >>> di.GetDataClassName() 'vtkPolyData' # Get information about point data. >>> pdi = di.PointData # We are now directly accessing the wrapper for a VTK class >>> len(pdi) 1 # Get information for a point array >>> ai = pdi >>> ai.GetRange(0) (0.0, 0.5)
When meta-data is not enough and you need access to the raw data, you can use Fetch() to bring it to the client side. Note that this function is provided by the servermanager module. Fetch() has three modes:
- Append all of the data together and bring it to the client (only available for polygonal and unstructured datasets). Note: Do not do this if data is large otherwise the client will run out of memory.
- Bring data from a given process to the client.
- Use a reduction algorithm and bring its output to the client. For example, find the minimum value of an attribute.
Here is a demonstration.
>>> from paraview.simple import * >>> Connect("kamino") Connection (kamino:11111) >>> s = Sphere() # Get the whole sphere. DO NOT DO THIS IF THE DATA IS LARGE otherwise # the client will run out of memory. >>> allsphere = servermanager.Fetch(s) getting appended use append poly data filter >>> allsphere.GetNumberOfPolys() 96 # Get the piece of the sphere on process 0. >>> onesphere = servermanager.Fetch(s, 0) getting node 0 >>> onesphere.GetNumberOfPolys() 48 # Apply the elevation filter so that we have a useful scalar array. >>> elev = Elevation(s) # We will use the MinMax algorithm to compute the minimum value of # elevation. MinMax will be first applied on each processor. The results # will then be gathered to the first node. MinMax will be then applied # to the gathered results. # We first create MinMax without an input. >>> mm = MinMax(None) # Set it to compute min >>> mm.Operation = "MIN" # Get the minimum >>> mindata = servermanager.Fetch(elev, mm, mm) applying operation # The result is a vtkPolyData with one point >>> mindata.GetPointData().GetNumberOfArrays() 2 >>> a0 = mindata.GetPointData().GetArray(1) >>> a0.GetName() 'Elevation' >>> a1.GetTuple1(0) 0.0
Representations and Views
Once a pipeline is created, it can be rendered using representations and views. A view is essentially a “window” in which multiple representations can be displayed. When the view is a VTK view (such as RenderView), this corresponds to a collection of objects including vtkRenderers and a vtkRenderWindow. However, there is no requirement for a view to be a VTK view or to render anything. A representation is a collection of objects, usually a pipeline, that takes a data object, converts it to something that can be rendered and renders it. When the view is a VTK view, this corresponds to a collection of objects including geometry filters, level-of-detail algorithms, vtkMappers and vtkActors. The simple module automatically creates a view after connecting to a server (including the built-in connection when using the stand-alone mode). Furthermore, the simple module creates a representation the first time a pipeline object is displayed with Show(). It is easy to create new views.
>>> view = CreateRenderView()
CreateRenderView() is a special method that creates the render view appropriate for the ActiveConnection (or for another connection specified as an argument). It returns a sub-class of Proxy. Like the constructor of Proxy, it can take an arbitrary number of keyword arguments to set initial values for properties. Note that ParaView makes the view that was created last the active view. When using Show() without a view argument, the pipeline is shown in the active view. You can get a list of views as well as the active view as follows
>>> GetRenderViews() [<paraview.servermanager.RenderView object at 0xaf64ef0>, <paraview.servermanager.RenderView object at 0xaf64b70>] >>> GetActiveView() <paraview.servermanager.RenderView object at 0xaf64b70>
You can also change the active view using SetActiveView().
Once you have a render view, you can use pass it to show in order to select in which view a pipeline object is displayed. You can also pass it to Render() to select which view is rendered.
>>> Show(elev, GetRenderViews()) <paraview.servermanager.GeometryRepresentation object at 0xaf64e30> >>> Render(GetRenderViews())
Notice that Show() returns a representation object (aka DisplayProperties in the simple module). This object can be used to manipulate how the pipeline object is displayed in the view. You can also access the display properties of an object using GetDisplayProperties().
>>> dp = GetDisplayProperties(elev) >>> dp <paraview.servermanager.GeometryRepresentation object at 0xaf649d0>
Display properties and views have a large number of documented properties some of which are poorly documented. We will cover some them here.
>>> from paraview.simple import * # Create a simple pipeline >>> sph = Sphere() >>> elev = Elevation(sph) >>> Show(elev) >>> Render() # Set the representation type of elev >>> dp = GetDisplayProperties(elev) >>> dp.Representation = 'Points' # Here is how you get the list of representation types >>> dp.GetProperty("Representation").Available ['Outline', 'Points', 'Wireframe', 'Surface', 'Surface With Edges'] >>> Render() # Change the representation to wireframe >>> rep.Representation = 'Wireframe >>> Render() # Let’s get some information about the output of the elevation # filter. We want to color the representation by one of it’s # arrays. # Second array = Elevation. Interesting. Let’s use this one. >>> ai = elev.PointData >>> ai.GetName() 'Elevation' # What is its range? >>> ai.GetRange() (0.0, 0.5) # To color the representation by an array, we need to first create # a lookup table. We use the range of the Elevation array >>> rep.LookupTable = MakeBlueToRedLT(0, 0.5) >>> rep.ColorAttributeType = 'POINT_DATA' >>> rep.ColorArrayName = 'Elevation' # color by Elevation >>> Render()
Here is the result: ￼
Once you create a scene, you will probably want to interact with the camera and ResetCamera() is likely to be insufficient. In this case, you can directly get the camera from the view and manipulate it. GetActiveCamera() returns a VTK object (not a proxy) with which you can interact.
>>> camera = GetActiveCamera() >>> camera <libvtkCommonPython.vtkCamera vtkobject at 0xe290> >>> camera.Elevation(45) >>> Render()
Another common thing to do is to save the view as an image. For this purpose, you can use the WriteImage() method provided by the view:
The resulting image.png looks like this. See the documentation for WriteImage() for details on choosing file type as well as a magnification factor to save images larger than the view size.