The Shapes¶
The scatman.Shapes
submodule contains a set of objects that aim to describe different kinds of shapes.
All shapes derive from an abstract base class scatman.Shapes.Shape
. Being an abstract class, it cannot be used directly, i.e. it is not possible to instantiate an object of that type:
>>> import scatman
>>> MyShape = scatman.Shapes.Shape()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: scatman.Shapes.Shape: No constructor defined!
Shape orientation¶
A set of parameters that is common among all the shapes is the one that defines the orientation in space. The three parameters are:
rotation:  rotation of the shape along its main axis (dashed red line in the figure), in degrees. 


latitude, longitude:  
coordinates at which the main axis of the shape points. These coordinates are, in particular, defined with respect to the beam propagation axis, depicted in blue in the figure below.

Optical properties¶
The scattering pattern is affected also by the optical properties of the sample material, defined by the complex refractive index . However, it is often more convenient, and well spread in literature, to identify the optical properties via two quantities:
 : refractive index decrease
 : absorpion coefficient
Their relationship with the complex refractive index is defined by the following relations:
Thus, for each shape, the following parameters define the optical properties of the sample:
delta:  decrease of the real part of the refractive index 

beta:  absorption coefficient 
Position within the beam focus¶
If the simulation includes also the photons statistics (i.e. if a detector that is not the MSFT is used) the photon density is relevant for the correct simulation of the diffraction pattern. In a real experiment, even if the photon density is constant, the amount of scattered radiation for similar samples can vary a lot. This derives from the fact that the beam focus doesn’t have a perfect flat intensity spatial distribution, but can be modelled as a Gaussian 2D profile, with the peak intensity in the center. The scattered intensity thus depends also on how far the shape is from the peak intensity, as the following figure depicts:
To simulate this behavior, especially when the simulation involves a dataset, the parameter beam_position
can be provided to the shape constructor. This value represents the distance from the peak intensity in units of , where is the standard deviation of the 2D Gaussian beam profile in the interaction zone.
Predefined Shapes¶

class
scatman.Shapes.
Shape
¶ 
get
(self: scatman.Shapes.Shape, resolution: int = 80, os: int = 3) → Tuple[array, float]¶ Get a voxel rendering of the shape.
Get a 3D rendering of the shape.
Parameters:  resolution – number of voxels in one dimension. The current implementation draws the shape inside a 3D cube with a total number of voxels equal to .
 oversampling – oversampling factor for each axis. When a shape is rendered on the 3D grid, the value for each voxel is assigned by sampling the shape in multiple points. For example, setting the value to 3 implies that a total of sample values are taken, within the voxel size, to assign its final value. Higher values mitigate the discretization artifacts, but increase the rendering computational cost.
Returns: A tuple containing a 3D numpy array containing the rendered shape, and float that provides the linear dimension of the voxels.

get_name
(self: scatman.Shapes.Shape) → str¶

get_size
(self: scatman.Shapes.Shape) → float¶


class
scatman.Shapes.
Ellipsoid
(self: scatman.Shapes.Ellipsoid, a: float, b: float, c: float, delta: float = 0, beta: float = 0.001, latitude: float = 90, longitude: float = 0, rotation: float = 0, beam_position: float = 0) → None¶ Bases:
scatman.Shapes.Shape
Define an Ellipsoid via the values of its three axes.
Parameters:

class
scatman.Shapes.
Dumbbell
(self: scatman.Shapes.Dumbbell, l: float, a: float, b: float, c: float, z: float, delta: float = 0, beta: float = 0.001, latitude: float = 90, longitude: float = 0, rotation: float = 0, beam_position: float = 0) → None¶ Bases:
scatman.Shapes.Shape
Define a dumbbell shape via the parameters described in the picture.
Parameters:

class
scatman.Shapes.
ShellSphere
(self: scatman.Shapes.ShellSphere, r: float, s: float, t: float = 0, delta: float = 0, beta: float = 0.01, delta_shell: float = 0.01, beta_shell: float = 0.001, beam_position: float = 0) → None¶ Bases:
scatman.Shapes.Shape
Define a sphere with optical properties distributed in a coreshell manner. Due to the simmetry of the sphere, the orientation parameters have no effect and are thus not available for this shape.
Parameters:

class
scatman.Shapes.
MoonSphere
(self: scatman.Shapes.MoonSphere, r: float, s: float, t: float = 0, delta: float = 0, beta: float = 0.01, delta_shell: float = 0.01, beta_shell: float = 0.001, beam_position: float = 0) → None¶ Bases:
scatman.Shapes.Shape
Define a sphere with optical properties distributed in a coreshell manner, where the shell has a falling moon shape oriented in the direction of the incoming beam. Due to the simmetry of the sphere, the orientation parameters have no effect and are thus not available for this shape.
Parameters:
Custom Shapes¶
Up to now, each presented shape can only describe a small subset of all the possible sample shapes. Here two additional kinds of shapes are presented, that allow much more flexibility in the shape defition, but on the other side are not so straightforward to define, as it was for the other ones.
Custom Radial Function¶
An useful way to identify a 3D shape is to restrict to the (still wide) subset of shapes whose surface can be identified by a function , where is the sample radius and are the angles in spherical coordinates.
The shape scatman.Shapes.RadialMap()
is provided exactly for this purpose. At construction time, it requires a 2D numpy array that defines the radius as function of and .
The following code provides a minimal example of its functionalities.
import scatman
import numpy as np
radii_values = np.ones([100,200]) # set the same radius at any angle theta and phi
scatman.set_experiment(wavelength=30, angle=30, resolution=512) # set the experimental parameters
detector = scatman.Detectors.Ideal(mask_radius=50) # set the detector, with a central hole of 50 pixels
shape = scatman.Shapes.RadialMap(a = 400, radii=radii_values) # a is a scaling factor (see class documentation)
# radii is the radial map
pattern=detector.acquire(shape)
import render_sh # you can find it in the "tests" folder. Requires the Vtk python module
render_sh.Scene([pattern], [shape]) # render the shape and the simulated diffraction pattern together, in a fancy way
As expected, the result is just a sphere, because the radius is a flat function.
A second, fancier, example is to load an image and use it as a radial function. The following image will be used:
The following code should do the job. First, we load the image (you can found it in the tests folder) and convert it to a 2D numpy array
from PIL import Image
import numpy as np
image = Image.open('NUX.png') # load the grayscale image and
nux = np.copy(np.asarray(image)[:,:,0]) # convert it to a 2D numpy array
nux=nux/np.max(nux)
Then, we do the same steps as the previous example, where the radii passed to the shape constructor are now provided by the nux
2D numpy array.
import scatman
scatman.set_experiment(wavelength=30, angle=30, resolution=512) # set the experimental parameters
detector = scatman.Detectors.Ideal(mask_radius=50) # set the detector, with a central hole of 50 pixels
shape = scatman.Shapes.RadialMap(a=700, radii=nux, \ # a is a scaling factor (see class documentation)
latitude=0, longitude=0, rotation=58) # the nux image is used as radial map
pattern=detector.acquire(shape)
import render_sh # you can find it in the "tests" folder. Requires the Vtk python module
render_sh.Scene([pattern], [shape]) # render the shape and the simulated diffraction pattern together, in a fancy way

class
scatman.Shapes.
RadialMap
(self: scatman.Shapes.RadialMap, radii: numpy.ndarray[float32], a: float = 1, delta: float = 0, beta: float = 0.001, latitude: float = 90, longitude: float = 0, rotation: float = 0, beam_position: float = 0) → None¶ Bases:
scatman.Shapes.Shape
Define a RadialMap.
Parameters:
Cube Mapping¶
The main drawback of the RadialMap
object described above regards the strongly uneven way of sampling a sphere surface. In particular, the amount of sampling points per unit area is much higher at the poles than at the equator.
However, a perfectly uniform sampling of a sphere surface is a challanging task, and approximate methods (that is, giving just fairly uniform samplings) are often used.
Among them, the Cube Mapping is one of the easiest and most efficient ways to accomplish a fairly uniform sampling of a function (the radius in this case) defined on a sphere surface.
Cube Mapping usess the six faces of a cube as the map shape. The spherical coordinates are projected onto the sides of a cube. Each face is a square matrix, that gives the values of the sample radius in the respective direction.
To define a CubeMap
object, a 3D array of size [6, dim, dim]
has to be defined. Each index in the first dimension identifies a face of the cube, which is a 2D square matrix of size [dim, dim]
.
In the left side of upper image, the organization of the different faces is shown. In the upper left of the square faces, the index of the face is shown. The letter at the center, instead, indicates the axis normal to the face. Finally, the color indicates if the face is directed in the positive direction (blue) or negative direction (red). For example, the face with index 4 is normal to the z axis, oriented towards its positive side. Face 2, instead, is normal to the x axis, and looks towards its negative versus.
The right side of the figure represents how the 6 faces are organized and oriented into the 3D space, relative to the beam propagation direction.
The following code is a trivial example on how to define the CubeMap
shape object.
First, as usual, the Scatman module must be imported, the relevant experimental parameters should be set, and the detector defined.
import scatman
import random
import numpy as np
scatman.set_experiment(wavelength=40, angle=30, resolution=512)
detector = scatman.Detectors.MSFT()
Then, the 6 faces of the cube has to be defined:
radii=np.ones([6, 7, 7])*400
radii[:,::2,::2]=600
radii[1,2,2]=800
The following figure is a representation of the 6 faces defined in the above code
Then, the shape object can be constructed, passing the radii array defined above, and the simulation carried on in the usual way:
shape = scatman.Shapes.CubeMap(radii=radii, delta=0.02, beta=0.01)
pattern = detector.acquire(shape)
The following picture is a rendering of the shape and the simulated diffraction pattern:
Note
Despite the “low” resolution of the cube faces (6 by 6 pixels in the example), the rendering of the shape looks smooth. This is because the radius value is obtained by cubic interpolation during the simulation.

class
scatman.Shapes.
CubeMap
(self: scatman.Shapes.CubeMap, radii: numpy.ndarray[float32], a: float = 1, delta: float = 0, beta: float = 0.001, latitude: float = 90, longitude: float = 0, rotation: float = 0, beam_position: float = 0) → None¶ Bases:
scatman.Shapes.Shape
Define a sample’s shape using a Cube Mapping of its radius.
Parameters:  radii – 3D array providing the radii. The first dimension of the array must be equal to 6, and represents the 6 faces of the cube. Second and third dimesions should be equal, which means that the faces of the cube must be square matrices.
 a – scaling factor: if a>0 the shape will be scaled such that the maximum radius has a value equal to a.
 delta, beta – see here
 latitude, longitude, rotation – see here.
 beam_position – see here.

get_radii
(self: scatman.Shapes.CubeMap) → array¶
Spherical Harmonics Expansion¶
Following what was discussed in the previous section about the scatman.Shapes.RadialMap()
, it is clear that manually providing the radial map is very ductile, but not straightforward.
However, there exists a convenient way to express a radial map with fewer parameters, exploiting the expansion in Spherical Harmonics . Spherical harmonics are an orthonormal base for any function defined on a sphere, i.e. . Being this the case of , spherical harmonics may represent a convenient and more compact way to define 3D shapes.
The class scatman.Shapes.SphericalHarmonics()
, which inherits from scatman.Shapes.RadialMap()
, enables the user to define the radial map based on spherical harmonics. Spherical harmonics may have different definitions, based on different conventions about the normalization factors. Here we use the following definition:
where and are the associated Legendre polynomials , that can be defined in the following way:
These spherical harmonics are actually complexvalued. However, the radius is a real function. For this reason, real spherical harmonics will be used as a basis, following this definition:
The following example provides a simple overview over the scatman.Shapes.SphericalHarmonics()
functionalities.
Its use is very similar to scatman.Shapes.RadialMap()
, where, instead of passing the image of the radii, the indices and the coefficients of the spherical harmonics are provided.
First, as usual, the scatman module is imported, and the relevant parameters and objects are defined
import scatman
scatman.set_experiment(wavelength=30, angle=30, resolution=512) # set the experimental parameters
detector = scatman.Detectors.Ideal(mask_radius=50) # set the detector, with a central hole of 50 pixels radius
shapes=[] # empty list of shapes, that will be filled then with the different shape objects
Now, we start to define a shape with spherical harmonics. The first shape we define is the easiest one, that is just a sphere. A sphere in spherical harmonics is defined by just the spherical harmonic :
harmonics = [[0,0,1]]
shapes.append(scatman.Shapes.SphericalHarmonics(a=400, coefficients=harmonics))
where the array [0,0,1]
is a triplet that indicates , where the latter value is the coefficient. The parameter a
is a scaling factor, that defines the maximum radius of the shape.
A second shape can be defined with an additional coefficient, for example.
harmonics = [[0,0,1],
[2,1,0.25]]
shapes.append(scatman.Shapes.SphericalHarmonics(a=400, coefficients=harmonics, latitude=43, longitude=190, rotation=58))
This shape recreates, more or less, the scatman.Shapes.Dumbbell()
.
The amount of coefficients has no upper limit, and bizarre shapes can be easily created by setting the coefficients for higher spherical harmonics. For example:
harmonics = [ [0,0,1],
[1,1,0.2],
[2,1,0.06],
[7,6,0.08]]
shapes.append(scatman.Shapes.SphericalHarmonics(a=700, coefficients=harmonics, latitude=13, longitude=40, rotation=76))
Then, it is possible to perform the experiment and visualize the results:
patterns=detector.acquire_dataset(shapes)
import render_sh # you can find it in the "tests" folder. Requires the Vtk python module
render_sh.Scene(patterns, shapes) # render the shape and the simulated diffraction pattern together, in a fancy way

class
scatman.Shapes.
SphericalHarmonics
(self: scatman.Shapes.SphericalHarmonics, coefficients: numpy.ndarray[float32], a: float = 1, delta: float = 0, beta: float = 0.001, latitude: float = 90, longitude: float = 0, rotation: float = 0, beam_position: float = 0) → None¶ Bases:
scatman.Shapes.RadialMap
Define a shape basing on the spherical harmonics coefficients. See the documentation for a deeper description.
Parameters:
3D Volume¶

class
scatman.Shapes.
VolumeMap
(self: scatman.Shapes.VolumeMap, dx: float, data: numpy.ndarray[bool], delta: float = 0, beta: float = 0.001, latitude: float = 90, longitude: float = 0, rotation: float = 0, beam_position: float = 0) → None¶ Bases:
scatman.Shapes.Shape
Define a custom sample, by directly providing its 3D rendering. Here, the rendering is provided as a 3D boolean array, where True indicates the presence of the material, while False is vacuum. The 3D array provided as input can be of any size, and it is automatically rescaled to match the experimental conditions via interpolation.
Parameters:

class
scatman.Shapes.
VolumeDistribution
(self: scatman.Shapes.VolumeDistribution, dx: float, data: numpy.ndarray[complex64], latitude: float = 90, longitude: float = 0, rotation: float = 0, beam_position: float = 0) → None¶ Bases:
scatman.Shapes.Shape
Define a custom sample, by directly providing its 3D complex refractive index distribution by means of a 3D complexvalued array. The 3D array provided as input can be of any size, and it is automatically rescaled to match the experimental conditions via interpolation. Values equal to 1 correspond to the optical properties of vacuum.
Parameters: