ModuleSource

Provides a definition of sources of light. A source can be either a composite source (a sum of sources) or a simple source. A simple source is defined by 4 parameters:

  • Spectrum
  • Angular power distribution
  • Ray origins distribution
  • Ray directions distribution A reason to use composite sources is for when the power and ray distributions depend on the wavelength. Another source option is the source containing just a list of rays that can be prepared manually. Finally another source option is one used for alignment, containing a single ray.

Created in 2019

@author: Anthony Guillaume and Stefan Haessler

  1"""
  2Provides a definition of sources of light.
  3A source can be either a composite source (a sum of sources) or a simple source.
  4A simple source is defined by 4 parameters:
  5- Spectrum
  6- Angular power distribution
  7- Ray origins distribution
  8- Ray directions distribution
  9A reason to use composite sources is for when the power and ray distributions depend on the wavelength.
 10Another source option is the source containing just a list of rays that can be prepared manually.
 11Finally another source option is one used for alignment, containing a single ray.
 12
 13
 14Created in 2019
 15
 16@author: Anthony Guillaume and Stefan Haessler
 17"""
 18
 19# %% Modules
 20import ARTcore.ModuleOpticalRay as mray
 21import ARTcore.ModuleGeometry as mgeo
 22from ARTcore.ModuleGeometry import Point, Vector, Origin
 23import ARTcore.ModuleProcessing as mp
 24
 25from ARTcore.DepGraphDefinitions import UniformSpectraCalculator
 26
 27import numpy as np
 28from abc import ABC, abstractmethod
 29import logging
 30
 31logger = logging.getLogger(__name__)
 32
 33# %% Abstract base classes for sources
 34class Source(ABC):
 35    """
 36    Abstract base class for sources.
 37    Fundamentally a source just has to be able to return a list of rays.
 38    To do so, we make it callable.
 39    """
 40    @abstractmethod
 41    def __call__(self,N):
 42        pass
 43
 44class PowerDistribution(ABC):
 45    """
 46    Abstract base class for angular power distributions.
 47    """
 48    @abstractmethod
 49    def __call__(self, Origins, Directions):
 50        """
 51        Return the power of the rays coming 
 52        from the origin points Origins to the directions Directions.
 53        """
 54        pass
 55class RayOriginsDistribution(ABC):
 56    """
 57    Abstract base class for ray origins distributions.
 58    """
 59    @abstractmethod
 60    def __call__(self, N):
 61        """
 62        Return the origins of N rays.
 63        """
 64        pass
 65
 66class RayDirectionsDistribution(ABC):
 67    """
 68    Abstract base class for ray directions distributions.
 69    """
 70    @abstractmethod
 71    def __call__(self, N):
 72        """
 73        Return the directions of N rays.
 74        """
 75        pass
 76
 77class Spectrum(ABC):
 78    """
 79    Abstract base class for spectra.
 80    """
 81    @abstractmethod
 82    def __call__(self, N):
 83        """
 84        Return the wavelengths of N rays.
 85        """
 86        pass
 87
 88# %% Specific power distributions
 89class SpatialGaussianPowerDistribution(PowerDistribution):
 90    """
 91    Spatial Gaussian power distribution.
 92    """
 93    def __init__(self, Power, W0):
 94        self.Power = Power
 95        self.W0 = W0
 96
 97    def __call__(self, Origins, Directions):
 98        """
 99        Return the power of the rays coming 
100        from the origin points Origins to the directions Directions.
101        """
102        return self.Power * np.exp(-2 * np.linalg.norm(Origins, axis=1) ** 2 / self.W0 ** 2)
103    
104class AngularGaussianPowerDistribution(PowerDistribution):
105    """
106    Angular Gaussian power distribution.
107    """
108    def __init__(self, Power, Divergence):
109        self.Power = Power
110        self.Divergence = Divergence
111
112    def __call__(self, Origins, Directions):
113        """
114        Return the power of the rays coming 
115        from the origin points Origins to the directions Directions.
116        """
117        return self.Power * np.exp(-2 * np.arccos(np.dot(Directions, [0, 0, 1])) ** 2 / self.Divergence ** 2)
118
119class GaussianPowerDistribution(PowerDistribution):
120    """
121    Gaussian power distribution.
122    """
123    def __init__(self, Power, W0, Divergence):
124        self.Power = Power
125        self.W0 = W0
126        self.Divergence = Divergence
127
128    def __call__(self, Origins, Directions):
129        """
130        Return the power of the rays coming 
131        from the origin points Origins to the directions Directions.
132        """
133        return self.Power * np.exp(-2 * (Origins-mgeo.Origin).norm ** 2 / self.W0 ** 2) * np.exp(-2 * np.array(np.arccos(np.dot(Directions, mgeo.Vector([1, 0, 0])))) ** 2 / self.Divergence ** 2)
134
135class UniformPowerDistribution(PowerDistribution):
136    """
137    Uniform power distribution.
138    """
139    def __init__(self, Power):
140        self.Power = Power
141
142    def __call__(self, Origins, Directions):
143        """
144        Return the power of the rays coming 
145        from the origin points Origins to the directions Directions.
146        """
147        return self.Power * np.ones(len(Origins))
148
149# %% Specific ray origins distributions
150class PointRayOriginsDistribution(RayOriginsDistribution):
151    """
152    Point ray origins distribution.
153    """
154    def __init__(self, Origin):
155        self.Origin = Origin
156
157    def __call__(self, N):
158        """
159        Return the origins of N rays.
160        """
161        return mgeo.PointArray([self.Origin for i in range(N)])
162    
163class DiskRayOriginsDistribution(RayOriginsDistribution):
164    """
165    Disk ray origins distribution. Uses the Vogel spiral to initialize the rays.
166    """
167    def __init__(self, Origin, Radius, Normal = mgeo.Vector([0, 0, 1])):
168        self.Origin = Origin
169        self.Radius = Radius
170        self.Normal = Normal
171
172    def __call__(self, N):
173        """
174        Return the origins of N rays.
175        """
176        MatrixXY = mgeo.SpiralVogel(N, self.Radius)
177        q = mgeo.QRotationVector2Vector(mgeo.Vector([0, 0, 1]), self.Normal)
178        return mgeo.PointArray([self.Origin + mgeo.Vector([MatrixXY[i, 0], MatrixXY[i, 1], 0]) for i in range(N)]).rotate(q)
179    
180# %% Specific ray directions distributions
181class UniformRayDirectionsDistribution(RayDirectionsDistribution):
182    """
183    Uniform ray directions distribution.
184    """
185    def __init__(self, Direction):
186        self.Direction = Direction
187
188    def __call__(self, N):
189        """
190        Return the directions of N rays.
191        """
192        return mgeo.VectorArray([self.Direction for i in range(N)])
193
194class ConeRayDirectionsDistribution(RayDirectionsDistribution):
195    """
196    Cone ray directions distribution. Uses the Vogel spiral to initialize the rays.
197    """
198    def __init__(self, Direction, Angle):
199        self.Direction = Direction
200        self.Angle = Angle
201
202    def __call__(self, N):
203        """
204        Return the directions of N rays.
205        """
206        Height = 1
207        Radius = Height * np.tan(self.Angle)
208        MatrixXY = mgeo.SpiralVogel(N, Radius)
209        q = mgeo.QRotationVector2Vector(mgeo.Vector([0, 0, 1]), self.Direction)
210        return mgeo.VectorArray([[MatrixXY[i, 0], MatrixXY[i, 1], Height] for i in range(N)]).rotate(q).normalized()
211
212
213# %% Specific spectra
214class SingleWavelengthSpectrum(Spectrum):
215    """
216    Single wavelength spectrum.
217    """
218    def __init__(self, Wavelength):
219        self.Wavelength = Wavelength
220
221    def __call__(self, N):
222        """
223        Return the wavelengths of N rays.
224        """
225        return np.ones(N) * self.Wavelength
226
227class UniformSpectrum(Spectrum):
228    """
229    Uniform spectrum.
230    Can be specified as a r
231    """
232    def __init__(self, eVMax = None, eVMin = None, eVCentral = None, 
233                 lambdaMax = None, lambdaMin = None, lambdaCentral = None, 
234                 eVWidth = None, lambdaWidth = None):
235        # Using the DepSolver, we calculate the minimum and maximum wavelengths
236        values, steps = UniformSpectraCalculator().calculate_values(
237            lambda_min = lambdaMin, 
238            lambda_max = lambdaMax, 
239            lambda_center = lambdaCentral, 
240            lambda_width = lambdaWidth,
241            eV_min = eVMin,
242            eV_max = eVMax,
243            eV_center = eVCentral,
244            eV_width = eVWidth
245        )
246        self.lambdaMin = values['lambda_min']
247        self.lambdaMax = values['lambda_max']
248    def __call__(self, N):
249        """
250        Return the wavelengths of N rays.
251        """
252        return np.linspace(self.lambdaMin, self.lambdaMax, N)
253
254
255# %% Simple sources
256class SimpleSource(Source):
257    """
258    A simple source is defined by 4 parameters:
259    - Spectrum
260    - Power distribution
261    - Ray origins distribution
262    - Ray directions distribution
263    """
264
265    def __init__(self, Spectrum, PowerDistribution, RayOriginsDistribution, RayDirectionsDistribution):
266        self.Spectrum = Spectrum
267        self.PowerDistribution = PowerDistribution
268        self.RayOriginsDistribution = RayOriginsDistribution
269        self.RayDirectionsDistribution = RayDirectionsDistribution
270
271    def __call__(self, N):
272        """
273        Return a list of N rays from the simple source.
274        """
275        Wavelengths = self.Spectrum(N)
276        Origins = self.RayOriginsDistribution(N)
277        Directions = self.RayDirectionsDistribution(N)
278        Powers = self.PowerDistribution(Origins, Directions)
279        RayList = []
280        for i in range(N):
281            RayList.append(mray.Ray(Origins[i], Directions[i], wavelength=Wavelengths[i], number=i, intensity=Powers[i]))
282        return mray.RayList.from_list(RayList)
283
284
285class ListSource(Source):
286    """
287    A source containing just a list of rays that can be prepared manually.
288    """
289    def __init__(self, Rays):
290        self.Rays = Rays
291
292    def __call__(self, N):
293        """
294        Return the list of rays.
295        """
296        if N < len(self.Rays):
297            return self.Rays[:N]
298        elif N>len(self.Rays):
299            logger.warning("Requested number of rays is greater than the number of rays in the source. Returning the whole source.")
300            return self.Rays    
301        return mray.RayList.from_list(self.Rays)
logger = <Logger ARTcore.ModuleSource (WARNING)>
class Source(abc.ABC):
35class Source(ABC):
36    """
37    Abstract base class for sources.
38    Fundamentally a source just has to be able to return a list of rays.
39    To do so, we make it callable.
40    """
41    @abstractmethod
42    def __call__(self,N):
43        pass

Abstract base class for sources. Fundamentally a source just has to be able to return a list of rays. To do so, we make it callable.

class PowerDistribution(abc.ABC):
45class PowerDistribution(ABC):
46    """
47    Abstract base class for angular power distributions.
48    """
49    @abstractmethod
50    def __call__(self, Origins, Directions):
51        """
52        Return the power of the rays coming 
53        from the origin points Origins to the directions Directions.
54        """
55        pass

Abstract base class for angular power distributions.

class RayOriginsDistribution(abc.ABC):
56class RayOriginsDistribution(ABC):
57    """
58    Abstract base class for ray origins distributions.
59    """
60    @abstractmethod
61    def __call__(self, N):
62        """
63        Return the origins of N rays.
64        """
65        pass

Abstract base class for ray origins distributions.

class RayDirectionsDistribution(abc.ABC):
67class RayDirectionsDistribution(ABC):
68    """
69    Abstract base class for ray directions distributions.
70    """
71    @abstractmethod
72    def __call__(self, N):
73        """
74        Return the directions of N rays.
75        """
76        pass

Abstract base class for ray directions distributions.

class Spectrum(abc.ABC):
78class Spectrum(ABC):
79    """
80    Abstract base class for spectra.
81    """
82    @abstractmethod
83    def __call__(self, N):
84        """
85        Return the wavelengths of N rays.
86        """
87        pass

Abstract base class for spectra.

class SpatialGaussianPowerDistribution(PowerDistribution):
 90class SpatialGaussianPowerDistribution(PowerDistribution):
 91    """
 92    Spatial Gaussian power distribution.
 93    """
 94    def __init__(self, Power, W0):
 95        self.Power = Power
 96        self.W0 = W0
 97
 98    def __call__(self, Origins, Directions):
 99        """
100        Return the power of the rays coming 
101        from the origin points Origins to the directions Directions.
102        """
103        return self.Power * np.exp(-2 * np.linalg.norm(Origins, axis=1) ** 2 / self.W0 ** 2)

Spatial Gaussian power distribution.

SpatialGaussianPowerDistribution(Power, W0)
94    def __init__(self, Power, W0):
95        self.Power = Power
96        self.W0 = W0
Power
W0
class AngularGaussianPowerDistribution(PowerDistribution):
105class AngularGaussianPowerDistribution(PowerDistribution):
106    """
107    Angular Gaussian power distribution.
108    """
109    def __init__(self, Power, Divergence):
110        self.Power = Power
111        self.Divergence = Divergence
112
113    def __call__(self, Origins, Directions):
114        """
115        Return the power of the rays coming 
116        from the origin points Origins to the directions Directions.
117        """
118        return self.Power * np.exp(-2 * np.arccos(np.dot(Directions, [0, 0, 1])) ** 2 / self.Divergence ** 2)

Angular Gaussian power distribution.

AngularGaussianPowerDistribution(Power, Divergence)
109    def __init__(self, Power, Divergence):
110        self.Power = Power
111        self.Divergence = Divergence
Power
Divergence
class GaussianPowerDistribution(PowerDistribution):
120class GaussianPowerDistribution(PowerDistribution):
121    """
122    Gaussian power distribution.
123    """
124    def __init__(self, Power, W0, Divergence):
125        self.Power = Power
126        self.W0 = W0
127        self.Divergence = Divergence
128
129    def __call__(self, Origins, Directions):
130        """
131        Return the power of the rays coming 
132        from the origin points Origins to the directions Directions.
133        """
134        return self.Power * np.exp(-2 * (Origins-mgeo.Origin).norm ** 2 / self.W0 ** 2) * np.exp(-2 * np.array(np.arccos(np.dot(Directions, mgeo.Vector([1, 0, 0])))) ** 2 / self.Divergence ** 2)

Gaussian power distribution.

GaussianPowerDistribution(Power, W0, Divergence)
124    def __init__(self, Power, W0, Divergence):
125        self.Power = Power
126        self.W0 = W0
127        self.Divergence = Divergence
Power
W0
Divergence
class UniformPowerDistribution(PowerDistribution):
136class UniformPowerDistribution(PowerDistribution):
137    """
138    Uniform power distribution.
139    """
140    def __init__(self, Power):
141        self.Power = Power
142
143    def __call__(self, Origins, Directions):
144        """
145        Return the power of the rays coming 
146        from the origin points Origins to the directions Directions.
147        """
148        return self.Power * np.ones(len(Origins))

Uniform power distribution.

UniformPowerDistribution(Power)
140    def __init__(self, Power):
141        self.Power = Power
Power
class PointRayOriginsDistribution(RayOriginsDistribution):
151class PointRayOriginsDistribution(RayOriginsDistribution):
152    """
153    Point ray origins distribution.
154    """
155    def __init__(self, Origin):
156        self.Origin = Origin
157
158    def __call__(self, N):
159        """
160        Return the origins of N rays.
161        """
162        return mgeo.PointArray([self.Origin for i in range(N)])

Point ray origins distribution.

PointRayOriginsDistribution(Origin)
155    def __init__(self, Origin):
156        self.Origin = Origin
Origin
class DiskRayOriginsDistribution(RayOriginsDistribution):
164class DiskRayOriginsDistribution(RayOriginsDistribution):
165    """
166    Disk ray origins distribution. Uses the Vogel spiral to initialize the rays.
167    """
168    def __init__(self, Origin, Radius, Normal = mgeo.Vector([0, 0, 1])):
169        self.Origin = Origin
170        self.Radius = Radius
171        self.Normal = Normal
172
173    def __call__(self, N):
174        """
175        Return the origins of N rays.
176        """
177        MatrixXY = mgeo.SpiralVogel(N, self.Radius)
178        q = mgeo.QRotationVector2Vector(mgeo.Vector([0, 0, 1]), self.Normal)
179        return mgeo.PointArray([self.Origin + mgeo.Vector([MatrixXY[i, 0], MatrixXY[i, 1], 0]) for i in range(N)]).rotate(q)

Disk ray origins distribution. Uses the Vogel spiral to initialize the rays.

DiskRayOriginsDistribution(Origin, Radius, Normal=Vector([0, 0, 1]))
168    def __init__(self, Origin, Radius, Normal = mgeo.Vector([0, 0, 1])):
169        self.Origin = Origin
170        self.Radius = Radius
171        self.Normal = Normal
Origin
Radius
Normal
class UniformRayDirectionsDistribution(RayDirectionsDistribution):
182class UniformRayDirectionsDistribution(RayDirectionsDistribution):
183    """
184    Uniform ray directions distribution.
185    """
186    def __init__(self, Direction):
187        self.Direction = Direction
188
189    def __call__(self, N):
190        """
191        Return the directions of N rays.
192        """
193        return mgeo.VectorArray([self.Direction for i in range(N)])

Uniform ray directions distribution.

UniformRayDirectionsDistribution(Direction)
186    def __init__(self, Direction):
187        self.Direction = Direction
Direction
class ConeRayDirectionsDistribution(RayDirectionsDistribution):
195class ConeRayDirectionsDistribution(RayDirectionsDistribution):
196    """
197    Cone ray directions distribution. Uses the Vogel spiral to initialize the rays.
198    """
199    def __init__(self, Direction, Angle):
200        self.Direction = Direction
201        self.Angle = Angle
202
203    def __call__(self, N):
204        """
205        Return the directions of N rays.
206        """
207        Height = 1
208        Radius = Height * np.tan(self.Angle)
209        MatrixXY = mgeo.SpiralVogel(N, Radius)
210        q = mgeo.QRotationVector2Vector(mgeo.Vector([0, 0, 1]), self.Direction)
211        return mgeo.VectorArray([[MatrixXY[i, 0], MatrixXY[i, 1], Height] for i in range(N)]).rotate(q).normalized()

Cone ray directions distribution. Uses the Vogel spiral to initialize the rays.

ConeRayDirectionsDistribution(Direction, Angle)
199    def __init__(self, Direction, Angle):
200        self.Direction = Direction
201        self.Angle = Angle
Direction
Angle
class SingleWavelengthSpectrum(Spectrum):
215class SingleWavelengthSpectrum(Spectrum):
216    """
217    Single wavelength spectrum.
218    """
219    def __init__(self, Wavelength):
220        self.Wavelength = Wavelength
221
222    def __call__(self, N):
223        """
224        Return the wavelengths of N rays.
225        """
226        return np.ones(N) * self.Wavelength

Single wavelength spectrum.

SingleWavelengthSpectrum(Wavelength)
219    def __init__(self, Wavelength):
220        self.Wavelength = Wavelength
Wavelength
class UniformSpectrum(Spectrum):
228class UniformSpectrum(Spectrum):
229    """
230    Uniform spectrum.
231    Can be specified as a r
232    """
233    def __init__(self, eVMax = None, eVMin = None, eVCentral = None, 
234                 lambdaMax = None, lambdaMin = None, lambdaCentral = None, 
235                 eVWidth = None, lambdaWidth = None):
236        # Using the DepSolver, we calculate the minimum and maximum wavelengths
237        values, steps = UniformSpectraCalculator().calculate_values(
238            lambda_min = lambdaMin, 
239            lambda_max = lambdaMax, 
240            lambda_center = lambdaCentral, 
241            lambda_width = lambdaWidth,
242            eV_min = eVMin,
243            eV_max = eVMax,
244            eV_center = eVCentral,
245            eV_width = eVWidth
246        )
247        self.lambdaMin = values['lambda_min']
248        self.lambdaMax = values['lambda_max']
249    def __call__(self, N):
250        """
251        Return the wavelengths of N rays.
252        """
253        return np.linspace(self.lambdaMin, self.lambdaMax, N)

Uniform spectrum. Can be specified as a r

UniformSpectrum( eVMax=None, eVMin=None, eVCentral=None, lambdaMax=None, lambdaMin=None, lambdaCentral=None, eVWidth=None, lambdaWidth=None)
233    def __init__(self, eVMax = None, eVMin = None, eVCentral = None, 
234                 lambdaMax = None, lambdaMin = None, lambdaCentral = None, 
235                 eVWidth = None, lambdaWidth = None):
236        # Using the DepSolver, we calculate the minimum and maximum wavelengths
237        values, steps = UniformSpectraCalculator().calculate_values(
238            lambda_min = lambdaMin, 
239            lambda_max = lambdaMax, 
240            lambda_center = lambdaCentral, 
241            lambda_width = lambdaWidth,
242            eV_min = eVMin,
243            eV_max = eVMax,
244            eV_center = eVCentral,
245            eV_width = eVWidth
246        )
247        self.lambdaMin = values['lambda_min']
248        self.lambdaMax = values['lambda_max']
lambdaMin
lambdaMax
class SimpleSource(Source):
257class SimpleSource(Source):
258    """
259    A simple source is defined by 4 parameters:
260    - Spectrum
261    - Power distribution
262    - Ray origins distribution
263    - Ray directions distribution
264    """
265
266    def __init__(self, Spectrum, PowerDistribution, RayOriginsDistribution, RayDirectionsDistribution):
267        self.Spectrum = Spectrum
268        self.PowerDistribution = PowerDistribution
269        self.RayOriginsDistribution = RayOriginsDistribution
270        self.RayDirectionsDistribution = RayDirectionsDistribution
271
272    def __call__(self, N):
273        """
274        Return a list of N rays from the simple source.
275        """
276        Wavelengths = self.Spectrum(N)
277        Origins = self.RayOriginsDistribution(N)
278        Directions = self.RayDirectionsDistribution(N)
279        Powers = self.PowerDistribution(Origins, Directions)
280        RayList = []
281        for i in range(N):
282            RayList.append(mray.Ray(Origins[i], Directions[i], wavelength=Wavelengths[i], number=i, intensity=Powers[i]))
283        return mray.RayList.from_list(RayList)

A simple source is defined by 4 parameters:

  • Spectrum
  • Power distribution
  • Ray origins distribution
  • Ray directions distribution
SimpleSource( Spectrum, PowerDistribution, RayOriginsDistribution, RayDirectionsDistribution)
266    def __init__(self, Spectrum, PowerDistribution, RayOriginsDistribution, RayDirectionsDistribution):
267        self.Spectrum = Spectrum
268        self.PowerDistribution = PowerDistribution
269        self.RayOriginsDistribution = RayOriginsDistribution
270        self.RayDirectionsDistribution = RayDirectionsDistribution
Spectrum
PowerDistribution
RayOriginsDistribution
RayDirectionsDistribution
class ListSource(Source):
286class ListSource(Source):
287    """
288    A source containing just a list of rays that can be prepared manually.
289    """
290    def __init__(self, Rays):
291        self.Rays = Rays
292
293    def __call__(self, N):
294        """
295        Return the list of rays.
296        """
297        if N < len(self.Rays):
298            return self.Rays[:N]
299        elif N>len(self.Rays):
300            logger.warning("Requested number of rays is greater than the number of rays in the source. Returning the whole source.")
301            return self.Rays    
302        return mray.RayList.from_list(self.Rays)

A source containing just a list of rays that can be prepared manually.

ListSource(Rays)
290    def __init__(self, Rays):
291        self.Rays = Rays
Rays