In this tutorial I will assume that you have downloaded the package and everything is working well in your computer. I will also assume that you usually work in Python.
Please, be aware that in order for sphviewer to work, you need to have matplotlib, numpy and scipy correctly installed in your system.
Attending suggestions from Pablo Benitez Llambay, I modified completely the structure of sphviewer after version 0.45. Please, do not use any older version because they are deprecated and very different from the new ones.
The latest version (v 0.136) is structured as a package. There are different modules stores in a main directory. I think in this way it will be easier to maintain and to develop different branches in case it is necessary.
Current sphviewer is divided into 4 different main clasess, which are:
Those classes represent an intuitive way of thinking. In fact, The final image is made after Render a set of Particles as their are looked by a Camera, which defines the Scene. According to this, I can say that:
In order to understand how sphviewer works, I show below a very simple example of its use. Let's make a disk with a radial density profile. I suggest to use 10000 particles. In the following example we are going to use numpy, matplotlib and (of course) sphviewer:
import numpy as np
import matplotlib.pyplot as plt
import sphviewer
n1 = 10000 #number of particles to make the disk
n2 = n1/3 # number of particles to make the background
r = np.random.rand(n1)
phi = 2*np.pi*np.random.rand(n1)
pos = np.zeros([3,n1], dtype=np.float32)
pos[0,:] = r*np.cos(phi)
pos[1,:] = r*np.sin(phi)
pos[2,:] = 0.1*np.random.rand(n1)
background = -2+4*np.random.rand(3,n2)
pos = np.concatenate((pos,background),axis=1)
fig = plt.figure(1, figsize=(10,5))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
ax1.plot(pos[0,:], pos[1,:], 'k.')
ax2.plot(pos[0,:], pos[2,:], 'k.')
[<matplotlib.lines.Line2D at 0x406d110>]
Before to continue, we have to assume some mass for the particles. I suggest to assume the same mass for all the particles (whith an arbitrary value of 1):
mass = np.ones(n1+n2)
Our goal is to render the particles with sphviewer. I will go through the steps faster:
Steps for making a plot in sphviewer are essentially 3:
Particles = sphviewer.Particles(pos,mass)
Scene = sphviewer.Scene(Particles)
Render = sphviewer.Render(Scene)
This is everything you need to know for making nice images. If you want to look at the final image, you can use the get_image() method from Render:
img = Render.get_image()
extent = Render.get_extent()
fig = plt.figure(1,figsize=(5,5))
ax1 = fig.add_subplot(111)
ax1.imshow(img, extent=extent, origin='lower', cmap='hot')
ax1.set_xlabel('X', size=15)
ax1.set_ylabel('Y', size=15)
<matplotlib.text.Text at 0x4788110>
You can realize that the image is not very clear. Actually it does not look very nice. What's going on? the problem is the dynamical range. The central region in the image is too dense and it spends most of the available colors. In this case, it should be better to use a logarithm scale. Sphviewer have some tools that helps to change the scale on the image:
Render.set_logscale()
img = Render.get_image()
extent = Render.get_extent()
fig = plt.figure(1,figsize=(5,5))
ax1 = fig.add_subplot(111)
ax1.imshow(img, extent=extent, origin='lower', cmap='hot', vmax=1.5)
ax1.set_xlabel('X', size=15)
ax1.set_ylabel('Y', size=15)
<matplotlib.text.Text at 0x47a5690>
This is much better. In fact, we can distiguish the central region, the disk and the background cube as well. However, the camera is still to far from the object. The distance from the Camera to the object was choosen automatically by sphviewer. You can define your own camera, or modify the parameters of the existing one. We are going to put the camera closer to the object:
camera = Scene.Camera.get_params()
print camera['r']
6.92480858525
So, we are at a distance of 6.92 in our self consistent units. Let's put the camera at a distance of 2.00
Scene.update_camera(r=2.00)
Render = sphviewer.Render(Scene)
Render.set_logscale()
img = Render.get_image()
extent = Render.get_extent()
fig = plt.figure(1,figsize=(5,5))
ax1 = fig.add_subplot(111)
ax1.imshow(img, extent=extent, origin='lower', cmap='hot', vmax=0.5)
ax1.set_xlabel('X', size=15)
ax1.set_ylabel('Y', size=15)
<matplotlib.text.Text at 0x48e2110>
Note that in the previous image, the limits go from -45 to +45. This is becauase the camera is looking at the particles and the camera is at a specified distance. In such a case, sphviewer returns the coordinates of the image in angular units. By default, the camera has a field of view of 90 degrees when zoom is 1. Increasing the zoom will reduce the field of view. So, it is possible to get nice results by changing the distance and zoom of the camera, just in the same way as you do when taking a photo. On the other hand, when setting r as 'infinity', the camera goes to the infinity and we get a parallel projection, and we can read the coordinates just looking at the axis:
Scene.update_camera(r='infinity')
Render = sphviewer.Render(Scene)
Render.set_logscale()
img = Render.get_image()
extent = Render.get_extent()
fig = plt.figure(1,figsize=(5,5))
ax1 = fig.add_subplot(111)
ax1.imshow(img, extent=extent, origin='lower', cmap='hot', vmax=0.5)
ax1.set_xlabel('X', size=15)
ax1.set_ylabel('Y', size=15)
<matplotlib.text.Text at 0x50c1210>
If we do not provide any size of the region we want to render when the camera is at the infinity, sphviewer will try to fix the volume automatically. I recomend to specify the size by using the extent argument in the camera parameters. By default extent parameter will be consider only when r='infinity'. Otherwise it is ignored:
Scene.update_camera(r='infinity')
Render = sphviewer.Render(Scene)
Render.set_logscale()
img1 = Render.get_image()
extent1 = Render.get_extent()
Scene.update_camera(r='infinity', extent=[-1,1,-1,1])
Render = sphviewer.Render(Scene)
Render.set_logscale()
img2 = Render.get_image()
extent2 = Render.get_extent()
Scene.update_camera(r='infinity', extent=[-4,4,-4,4])
Render = sphviewer.Render(Scene)
Render.set_logscale()
img3 = Render.get_image()
extent3 = Render.get_extent()
fig = plt.figure(1,figsize=(15,5))
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132)
ax3 = fig.add_subplot(133)
ax1.imshow(img1, extent=extent1, origin='lower', cmap='hot', vmax=0.5)
ax1.set_title('Automatic extent')
ax1.set_xlabel('X', size=15)
ax1.set_ylabel('Y', size=15)
ax2.imshow(img2, extent=extent2, origin='lower', cmap='hot', vmax=0.5)
ax2.set_title('extent=[-1,1,-1,1]')
ax2.set_xlabel('X', size=15)
ax2.set_ylabel('Y', size=15)
ax3.imshow(img3, extent=extent3, origin='lower', cmap='hot', vmax=0.5)
ax3.set_title('extent=[-4,4,-4,4]')
ax3.set_xlabel('X', size=15)
ax3.set_ylabel('Y', size=15)
<matplotlib.text.Text at 0x5be4d90>
From the previous example, we can see that extent is taken as how many units we want to see from the center of the image. It will clip de image only in x and y, but we will be still projecting all the particles along the full z range.
In many situations, when we want to look at the data, it is better to use the parallel projection, because it is easier to read. However, putting the camera at a certain distance from the object gives a really nice effect. I usually use the latest for making movies.
Finally, note that you can change the position of the camera respect to the object using spherical angles:
fig = plt.figure(1,figsize=(15,5))
t = [-90,-45, 0]
for i in xrange(1,4):
Scene.update_camera(r=2., t=t[i-1], extent=[-2,2,-2,2])
Render = sphviewer.Render(Scene)
Render.set_logscale()
img = Render.get_image()
extent = Render.get_extent()
ax = fig.add_subplot(1,3,i)
ax.imshow(img, extent=extent, origin='lower', cmap='hot', vmax=0.5)
ax.set_xlabel('X', size=15)
ax.set_ylabel('Y', size=15)
In principle, this is all you need to know about sphviewer. With this little tutorial you should be able to make plots of any set of particles. In the following I will explain in more detail different classes.
In the previouse example, the first step was to define the Particles class. As you may noted, Particles class stores all the information about the particles. This is used by sphviewer after to define the Scene. The parameters of the Particles class are:
sphviewer.Particles(pos, mass, hsml=None, nb=32, verbose=False)
pos and mass do not need any explanation as we already saw what they actually are. Regarding the optional parameters, like hsml and nb, they are not required at first, but they are needed in the internal structure of sphviewer. hsml is an array that stores the smoothing lenght of the particles. Smoothing lenghts are used in order to determine the contribution of each particles to the density field, which is projected along the line-of-sight to make the final image. There are different approachs for determining this quantity, but we define it as the distance to the nb neighbor particle. It is to say, in case nb is 32, the smoothing lenght of each particle will be the distance to its 32 closer neighbor. If, for some reason you have already computed this quantity, it is possible to pass it directly to sphviewer, avoiding in this way its calculation, which can be very slow. However, in most cases, we have not any smoothing lenght, like in the previous example. In such a case, sphviewer will compute it by itselft by searching the distance to the nb neighbor particle to each particle. Please, be aware that the computation of the smoothing lenght for a huge amount of particles may be very expensive.
Particles object has its own methods for making life easier. They can be divided into setting methods and getting methods.
Setting methods are useful for setting some property we forgot to pass at the time we instantiate the class, or to change some values already stored. They are:
On the other hand, the getting methods are useful for getting some information, like the smoothing lenghts, which could be nice in case we want to save it in a file. They are:
Finally, in many situations, we may want to look at the particles stored in the Particles class. In such a case, we could ask for the particles using the get_pos() method and ploting the after that. However, sphviewer has its own plotting methods:
where plane is a reference to 'xy', 'xz' or 'yz' projections. axis allows to define the axis for making the plot in case many axis are defined. If it is None (default value), plot method will search for the current active axis. Following the previous example, we can plot the particles distribution using the three different projections in the following way:
fig = plt.figure(1,figsize=(15,5))
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132)
ax3 = fig.add_subplot(133)
Particles.plot('xy',axis=ax1, color='r', markersize=1.)
Particles.plot('xz',axis=ax2, color='b', markersize=1.)
Particles.plot('yz',axis=ax3, color='g', markersize=1.)
The second object we defined in our example was the Scene:
sphviewer.Scene(Particles, Camera=None)
Scene object groups the Particles and the Camera. In other words, a Scene consist in a set of Particles which are seen from a Camera. Thinking in this way, it is almost obvius to understand why Scene needs information on the Particles and the position of the Camera. In general, defining a Camera involves the knowledge of the point at which you want to look and the distance from the Camera to that point. Definining it in advance time may be very annoying for most users, even for me, because usually we actually don't know what we are going to see! For that reason, I decided to provide a method for defining the parameters of the camera automatically. This methods is a method of the Scene class and it calls the Camera class. In case you wanted to define the camera by yourself, you should pass a Camera class after the Particles class.
Different methods of Scene class are summarized in the following:
Scene.set_autocamera(mode='minmax'): This method allows to change the mode that the autocamera uses to find the center of the image. By default, autocamera is using the 'minmax' mode, which search for minimum and maximum value of the x and y coordinates and put the center in the middle. Other possible modes are 'density', in which autocamera put the center on the particle which reachs the higher density. It is possible to choose also 'median' and 'mean'.
Scene.update_camera(kargs): This is probably one of the parameters you may want to use very often. When you are not happy with some parameters of the Camera, it is possible to change it by calling this method. I will explain it in more details below.
As Particles, Scene has different methods. They are:
Scene.get_extent(): It returns the extent array. It is needed in order to convert the coordinates of the final image (given in pixel units) into physical units. It is an array like [xmin,xmax,ymin,ymax]. It is to say, an array with the extreme values of the scene. As you may have already seen in the previous example, we used this parameter when using imshow.
Scene.get_scene(): After making the Scene, the coordinates of the Particles are projected according to their relative position to the Camera. This method returns the projected x and y coordinates of the Particles, their projected smoothing lenght and the index of the particles that are active in the Scene. In principle this method is for internal purposes and in most cases you don't need this information. However, it is interesting to think that with this method we can plot the Scene before render it, which should be faster. For doing so, I recommend to use the following method rather than this one.
Scene.plot(axis=None, kargs): This method is very similar to the one we already saw for Particles. It plots in the selected axis the projected positions of the Particles. Sometimes it is better to check the Scene before render it. It ensures that everything is going as expected.
The only argument this class takes is a Scene object. It renders the Scene and produces the final image. As every other classes, it has their own methods:
get_image(): For most users, it is the only relevant method. It returns the matrix of the image that was rendered. You can plot this image using matplotlib.pyplot.imshow() or you can save it with matplotlib.pyplot.imsabe()
get_min(): It returns the minimum value in the matrix of the image.
get_max(): It returns the maximum value in the matrix if the image.
get_extent(): The same as the get_extent method from Scene
get_logscale(): It returns True or False depending whether the logscale it True or False
set_logscale(): This method applies a logarithm scale to the image. It is useful to increase the dynamical range. The recipe is as follows: If M is the matrix of the image, get_image will return log10(M+1) as far as get_logscale() returns True.
save(outputfile, kargs): This method allows to save the image using several formats, like png, pdf, ps, etc. It uses matplotlib.pyplot.imsave in the background.
histogram(axis=None, kargs): It computes and shows the histogram of the image in the selected axis. kargs makes reference to any kargs accepted by matplotlib.pyplot.histogram(). This method is really useful for choosing the right minimum and maximum value to show in the image. It helps to fix the output dynamical range to highlight the interesting features in the image. It applied to our previous example gives:
fig = plt.figure(1,figsize=(5,5))
ax1 = fig.add_subplot(111)
Render.histogram(bins=100, log=True)
From here it is evident that most of the pixels are empty, and only a few pixels have the bigger densities. So, we can decide to bound the dynamical range between 0 and 0.6, without loosing too much information.
In most cases you do not need to define the Camera class by yourself. I recommend that you let Scene to make it. However, once Scene defines the Camera, you may want to check its parameters or eventually change them. The parameters of the camera are the following ones:
x, y, z: Position at which the camera points out
r : Distance from the camera to the (x,y,z) point. In case r='infinity', the camera goes to the infinity and you get objects as seen in a parallel projection.
t, p: Spherical angles to define the position of the camera. Once you pick up a point and a distance to that point, the camera can freely move within a sphere.
zoom: zoom is 1 by default and it defines the scale of the objects in the scene and the Field of View. By default, zoom=1 means a field-of-view of 90 degrees.
xsize, ysize: Number of pixels of the resulting image. With this parameter you set the Mpx of the camera. The general rule is as follows: the bigger the resolution, the higher the computational time for making the image.
extent: This parameter defines the bound of the image when r='infinity'. It is useful for making zoom when camera is at infinity (note that zoom has no meaning when camera is at infinity). Read the previous example in order to understand how to set it. In case you don't provide any extent when r='infinity', sphviewer will try to find some extent for you.
Again, as the previous classes, Camera has different methods:
get_params(): It returns a dictionary containing the parameters of the Camera.
set_params(): This method allows to define an specific parameter of the Camera, such as the zoom, r, and so on.
set_autocamera(): This method allows to set all the parameters of the camera automatically. The disadvantage is that it must be fed with a Particles object.
Finally, there is a final feature that can help you in many situations. Sometimes, you may want to know the position of the camera respect to the input set of Particles. This may be useful for making a movie, because we can see exaclty the trayectory of the Camera before rendering the scene. For that reason, Camera has its own plot method:
In order to show how does this final method work, let's take our previous example:
fig = plt.figure(1,figsize=(15,5))
plane = ['xy','xz','yz']
for i in xrange(3):
ax = fig.add_subplot(1,3,i)
Particles .plot(plane[i], axis=ax, markersize=0.5)
Scene .update_camera(r=1.5)
Scene.Camera.plot(plane[i], axis=ax)
plt.show()
I am planning to continue this tutorial with more examples in the future, so, it would be very useful for me to have feedback from potential users.