========================= The Shapes ========================= The :code:`scatman.Shapes` submodule contains a set of objects that aim to describe different kinds of shapes. All shapes derive from an *abstract* base class :code:`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: .. code:: >>> import scatman >>> MyShape = scatman.Shapes.Shape() Traceback (most recent call last): File "", line 1, in TypeError: scatman.Shapes.Shape: No constructor defined! .. _orientation_description: 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. :latitude: angle, in degrees, between the shape main axis and the *equator* defined by the beam propagation axis. :longitude: rotation, in degrees, along the beam propagation axis. .. figure:: figures/orientation.png :width: 50 % :align: center .. _optical_description: Optical properties ========================= The scattering pattern is affected also by the optical properties of the sample material, defined by the complex refractive index :math:`n=n' + \mathbf{i} n''`. However, it is often more convenient, and well spread in literature, to identify the optical properties via two quantities: - :math:`\delta`: refractive index decrease - :math:`\beta`: absorpion coefficient Their relationship with the complex refractive index :math:`n` is defined by the following relations: .. math:: :nowrap: \begin{eqnarray*} n' &=& 1 - \delta \\ n''&=& \beta\\ n &=& 1 - \delta + \mathbf{i} \beta \end{eqnarray*} Thus, for each shape, the following parameters define the optical properties of the sample: :delta: decrease of the real part of the refractive index :math:`\delta` :beta: absorption coefficient :math:`beta` .. _beam_position_description: 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: .. figure:: figures/beam_position.png :width: 50 % :align: center To simulate this behavior, especially when the simulation involves a dataset, the parameter :code:`beam_position` can be provided to the shape constructor. This value represents the distance from the peak intensity in units of :math:`\sigma`, where :math:`\sigma` is the standard deviation of the 2D Gaussian beam profile in the interaction zone. Pre-defined Shapes ========================= .. autoclass:: scatman.Shapes.Shape :members: .. :no-undoc-members: .. autoclass:: scatman.Shapes.Ellipsoid :show-inheritance: :members: .. autoclass:: scatman.Shapes.Dumbbell :show-inheritance: :members: .. autoclass:: scatman.Shapes.ShellSphere :show-inheritance: :members: .. autoclass:: scatman.Shapes.MoonSphere :show-inheritance: :members: 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 :math:`R(\theta, \phi)`, where :math:`R` is the sample radius and :math:`\theta, \phi` are the angles in spherical coordinates. The shape :meth:`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 :math:`\theta` and :math:`\phi`. The following code provides a minimal example of its functionalities. .. code:: 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 .. figure:: figures/sphere_radii.png :width: 80 % :align: center 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: .. figure:: figures/NUX_radii.png :width: 70 % :align: center 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 .. code:: 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 :code:`nux` 2D numpy array. .. code:: 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 .. figure:: figures/NUX_scatman.png :width: 80 % :align: center .. autoclass:: scatman.Shapes.RadialMap :show-inheritance: :members: Cube Mapping ------------ The main drawback of the :code:`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 :code:`CubeMap` object, a 3D array of size :code:`[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 :code:`[dim, dim]`. .. figure:: figures/cube_map.png :width: 100 % :align: center 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 :code:`CubeMap` shape object. First, as usual, the Scatman module must be imported, the relevant experimental parameters should be set, and the detector defined. .. code:: 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: .. code:: 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 .. figure:: figures/cube_map_exradii.png :width: 70 % :align: center Then, the shape object can be constructed, passing the radii array defined above, and the simulation carried on in the usual way: .. code:: 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: .. figure:: figures/cube_map_expattern.png :width: 90 % :align: center .. 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. .. autoclass:: scatman.Shapes.CubeMap :show-inheritance: :members: Spherical Harmonics Expansion ------------------------------ Following what was discussed in the previous section about the :meth:`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. :math:`f(\theta,\phi)`. Being this the case of :math:`R(\theta, \phi)`, spherical harmonics may represent a convenient and more compact way to define 3D shapes. The class :meth:`scatman.Shapes.SphericalHarmonics`, which inherits from :meth:`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: .. math:: Y_{\ell }^{m}(\theta ,\varphi )={\sqrt {{(2\ell +1)}{(\ell -m)! \over (\ell +m)!}}}\,P_{\ell }^{m}(\cos {\theta })\,e^{im\varphi } where :math:`|m| \leq \ell` and :math:`P_{\ell }^{m}(x)` are the `associated Legendre polynomials `_ , that can be defined in the following way: .. math:: P_{\ell }^{m}(x)={\frac {(-1)^{m}}{2^{\ell }\ell !}}(1-x^{2})^{m/2}\ {\frac {d^{\ell +m}}{dx^{\ell +m}}}(x^{2}-1)^{\ell } These spherical harmonics are actually complex-valued. However, the radius :math:`R(\theta, \phi)` is a real function. For this reason, `real spherical harmonics `_ will be used as a basis, following this definition: .. math:: Y_{\ell, m}= \begin{cases} \displaystyle {\sqrt {2}}\,\operatorname {Im} [{Y_{\ell }^{|m|}}] & {\text{if}}\ m<0\\ \displaystyle Y_{\ell }^{0} & {\text{if}}\ m=0\\ \displaystyle {\sqrt {2}}\,\operatorname {Re} [{Y_{\ell }^{m}}] & {\text{if}}\ m>0. \end{cases} The following example provides a simple overview over the :meth:`scatman.Shapes.SphericalHarmonics` functionalities. Its use is very similar to :meth:`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 .. code:: 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 :math:`Y_{\ell=0, m=0}`: .. code:: harmonics = [[0,0,1]] shapes.append(scatman.Shapes.SphericalHarmonics(a=400, coefficients=harmonics)) where the array :code:`[0,0,1]` is a triplet that indicates :math:`\ell=0, m=0, c`, where the latter value is the coefficient. The parameter :code:`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. .. code:: 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 :meth:`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: .. code:: 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: .. code:: 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 .. figure:: figures/sh_1.png :width: 80 % :align: center .. figure:: figures/sh_2.png :width: 80 % :align: center .. figure:: figures/sh_3.png :width: 80 % :align: center .. autoclass:: scatman.Shapes.SphericalHarmonics :show-inheritance: :members: 3D Volume --------- .. autoclass:: scatman.Shapes.VolumeMap :show-inheritance: :members: .. autoclass:: scatman.Shapes.VolumeDistribution :show-inheritance: :members: .. raw:: latex \newpage