ModuleOpticalRay

Created in Apr 2020

@author: Anthony Guillaume + Stefan Haessler + André Kalouguine

  1"""
  2Created in Apr 2020
  3
  4@author: Anthony Guillaume + Stefan Haessler + André Kalouguine
  5"""
  6# %% Modules
  7
  8import numpy as np
  9import logging
 10import ARTcore.ModuleGeometry as mgeo
 11logger = logging.getLogger(__name__)
 12import time
 13from dataclasses import dataclass, replace
 14
 15# %% Ray class definition
 16
 17@dataclass(slots=True)
 18class Ray:
 19    """
 20    Represents an optical ray from geometrical optics.
 21    In order to optimize lookups, Rays are represented as dataclasses.
 22    The mandatory attributes are:
 23    - point: mgeo.Point
 24    - vector: mgeo.Vector
 25    """
 26    point: mgeo.Point
 27    vector: mgeo.Vector
 28    path: tuple = (0.0,)
 29    number: int = None
 30    wavelength: float = None
 31    incidence: float = None
 32    intensity: float = None
 33
 34    def to_basis(self, r0, r, q):
 35        """
 36        Transforms the ray to the basis defined by the origin r0, the x-axis r and the y-axis q.
 37        """
 38        return Ray(
 39            self.point.to_basis(r0, r, q),
 40            self.vector.to_basis(r0, r, q),
 41            self.path,
 42            self.number,
 43            self.wavelength,
 44            self.incidence,
 45            self.intensity,
 46        )
 47    
 48    def from_basis(self, r0, r, q):
 49        """
 50        Transforms the ray from the basis defined by the origin r0, the x-axis r and the y-axis q.
 51        """
 52        return Ray(
 53            self.point.from_basis(r0, r, q),
 54            self.vector.from_basis(r0, r, q),
 55            self.path,
 56            self.number,
 57            self.wavelength,
 58            self.incidence,
 59            self.intensity,
 60        )
 61
 62    def rotate(self, q):
 63        """
 64        Rotates the ray by the quaternion q.
 65        """
 66        return Ray(
 67            self.point.rotate(q),
 68            self.vector.rotate(q),
 69            self.path,
 70            self.number,
 71            self.wavelength,
 72            self.incidence,
 73            self.intensity,
 74        )
 75    
 76    def translate(self, t):
 77        """
 78        Rotates the ray by the vector t
 79        """
 80        return Ray(
 81            self.point.translate(t),
 82            self.vector,
 83            self.path,
 84            self.number,
 85            self.wavelength,
 86            self.incidence,
 87            self.intensity,
 88        )
 89
 90    def __copy__(self):
 91        """
 92        Returns a new OpticalRay object with the same properties.
 93        """
 94        return replace(self)
 95
 96    def __hash__(self):
 97        point_tuple = tuple(self.point.reshape(1, -1)[0])
 98        vector_tuple = tuple(self.vector.reshape(1, -1)[0])
 99        return hash(
100            point_tuple + vector_tuple + (self.path, self.number, self.wavelength, self.incidence, self.intensity)
101        )
102
103
104@dataclass(slots=True)
105class RayList:
106    """
107    Class representing a list of rays in a form that is well suited to calculations.
108    Specifically, the points, vectors etc are all numpy arrays
109    It can then be converted to a list of Ray objects with the method 
110    """
111    point: mgeo.PointArray
112    vector: mgeo.VectorArray
113    path: np.ndarray
114    number: np.ndarray
115    wavelength: np.ndarray
116    incidence: np.ndarray
117    intensity: np.ndarray
118    N: int = None
119
120    @classmethod
121    def from_list(cls, rays):
122        N = len(rays)
123        if N == 0:
124            return cls(
125                mgeo.PointArray([[np.nan, np.nan, np.nan]]),
126                mgeo.VectorArray([[np.nan,np.nan,np.nan]]),
127                np.zeros((1,1)),
128                np.zeros(1),
129                np.zeros(1),
130                np.zeros(1),
131                np.zeros(1),
132                0,
133            )
134        N_r = len(rays[0].path)
135        points = np.zeros((N, 3))
136        vectors = np.zeros((N, 3))
137        paths = np.zeros((N, N_r))
138        numbers = np.zeros(N, dtype=int)
139        wavelengths = np.zeros(N)
140        incidences = np.zeros(N)
141        intensities = np.zeros(N)
142        for i, ray in enumerate(rays):
143            points[i:,] = ray.point
144            vectors[i:,] = ray.vector
145            paths[i:,] = ray.path
146            numbers[i] = ray.number
147            wavelengths[i] = ray.wavelength
148            incidences[i] = ray.incidence
149            intensities[i] = ray.intensity
150        return cls(
151            mgeo.PointArray(points),
152            mgeo.VectorArray(vectors),
153            paths,
154            numbers,
155            wavelengths,
156            incidences,
157            intensities,
158            len(rays),
159        )
160    
161    def __getitem__(self, key):
162        if isinstance(key, slice) or isinstance(key, list):
163            return RayList(
164                self.point[key],
165                self.vector[key],
166                self.path[key],
167                self.number[key],
168                self.wavelength[key],
169                self.incidence[key],
170                self.intensity[key],
171                len(key),
172            )
173        return Ray(
174            self.point[key],
175            self.vector[key],
176            tuple(self.path[key]),
177            self.number[key],
178            self.wavelength[key],
179            self.incidence[key],
180            self.intensity[key],
181        )
182    
183    def __len__(self):
184        return self.N
185    
186    def __iter__(self):
187        if self.N == 0:
188            return iter([])
189        return (Ray(point=self.point[i],
190                    vector=self.vector[i],
191                    path=tuple(self.path[i]),
192                    number=int(self.number[i]),
193                    wavelength=float(self.wavelength[i]),
194                    incidence=float(self.incidence[i]),
195                    intensity=float(self.intensity[i])) for i in range(self.N))
196
197    def to_basis(self, r0, r, q):
198        """
199        Transforms the ray to the basis defined by the origin r0, the x-axis r and the y-axis q.
200        """
201        return RayList(
202            self.point.to_basis(r0, r, q),
203            self.vector.to_basis(r0, r, q),
204            self.path,
205            self.number,
206            self.wavelength,
207            self.incidence,
208            self.intensity,
209            self.N
210        )
211    
212    def from_basis(self, r0, r, q):
213        """
214        Transforms the ray from the basis defined by the origin r0, the x-axis r and the y-axis q.
215        """
216        return RayList(
217            self.point.from_basis(r0, r, q),
218            self.vector.from_basis(r0, r, q),
219            self.path,
220            self.number,
221            self.wavelength,
222            self.incidence,
223            self.intensity,
224            self.N
225        )
226
227    def rotate(self, q):
228        """
229        Rotates the raylist by the quaternion q.
230        """
231        return RayList(
232            self.point.rotate(q),
233            self.vector.rotate(q),
234            self.path,
235            self.number,
236            self.wavelength,
237            self.incidence,
238            self.intensity,
239            self.N
240        )
241    
242    def translate(self, t):
243        """
244        Rotates the ray by the vector t
245        """
246        return RayList(
247            self.point.translate(t),
248            self.vector,
249            self.path,
250            self.number,
251            self.wavelength,
252            self.incidence,
253            self.intensity,
254            self.N
255        )
256
257    def __copy__(self):
258        """
259        Returns a new OpticalRay object with the same properties.
260        """
261        return RayList(
262            self.point.copy(),
263            self.vector.copy(),
264            self.path.copy(),
265            self.number.copy(),
266            self.wavelength.copy(),
267            self.incidence.copy(),
268            self.intensity.copy(),
269            self.N,
270        )
271
272    def __hash__(self):
273        points = self.point.reshape(1, -1)[0]
274        vectors = self.vector.reshape(1, -1)[0]
275        paths = self.path.reshape(1, -1)[0]
276        result = np.concatenate((points, vectors, paths, self.number, self.wavelength, self.incidence, self.intensity))
277        result = result[~np.isnan(result)]
278        result.flags.writeable = False
279        result_hash = hash(result.data.tobytes())
280        return result_hash
281
282    def __repr__(self) -> str:
283        intro = f"RayList with {self.N} rays\n"
284        average = mgeo.Vector(np.mean(self.vector)).normalized()
285        vectors = self.vector.normalized()
286        angles = np.arccos(np.clip(np.dot(vectors, average), -1.0, 1.0))
287        avg_string = "On average, the rays are oriented along the vector:\n" + str(average) + "\n"
288        NA = f"Numerical aperture: {np.max(np.sin(angles)):.3f}\n"
289        return intro +avg_string+ NA
logger = <Logger ARTcore.ModuleOpticalRay (WARNING)>
@dataclass(slots=True)
class Ray:
 18@dataclass(slots=True)
 19class Ray:
 20    """
 21    Represents an optical ray from geometrical optics.
 22    In order to optimize lookups, Rays are represented as dataclasses.
 23    The mandatory attributes are:
 24    - point: mgeo.Point
 25    - vector: mgeo.Vector
 26    """
 27    point: mgeo.Point
 28    vector: mgeo.Vector
 29    path: tuple = (0.0,)
 30    number: int = None
 31    wavelength: float = None
 32    incidence: float = None
 33    intensity: float = None
 34
 35    def to_basis(self, r0, r, q):
 36        """
 37        Transforms the ray to the basis defined by the origin r0, the x-axis r and the y-axis q.
 38        """
 39        return Ray(
 40            self.point.to_basis(r0, r, q),
 41            self.vector.to_basis(r0, r, q),
 42            self.path,
 43            self.number,
 44            self.wavelength,
 45            self.incidence,
 46            self.intensity,
 47        )
 48    
 49    def from_basis(self, r0, r, q):
 50        """
 51        Transforms the ray from the basis defined by the origin r0, the x-axis r and the y-axis q.
 52        """
 53        return Ray(
 54            self.point.from_basis(r0, r, q),
 55            self.vector.from_basis(r0, r, q),
 56            self.path,
 57            self.number,
 58            self.wavelength,
 59            self.incidence,
 60            self.intensity,
 61        )
 62
 63    def rotate(self, q):
 64        """
 65        Rotates the ray by the quaternion q.
 66        """
 67        return Ray(
 68            self.point.rotate(q),
 69            self.vector.rotate(q),
 70            self.path,
 71            self.number,
 72            self.wavelength,
 73            self.incidence,
 74            self.intensity,
 75        )
 76    
 77    def translate(self, t):
 78        """
 79        Rotates the ray by the vector t
 80        """
 81        return Ray(
 82            self.point.translate(t),
 83            self.vector,
 84            self.path,
 85            self.number,
 86            self.wavelength,
 87            self.incidence,
 88            self.intensity,
 89        )
 90
 91    def __copy__(self):
 92        """
 93        Returns a new OpticalRay object with the same properties.
 94        """
 95        return replace(self)
 96
 97    def __hash__(self):
 98        point_tuple = tuple(self.point.reshape(1, -1)[0])
 99        vector_tuple = tuple(self.vector.reshape(1, -1)[0])
100        return hash(
101            point_tuple + vector_tuple + (self.path, self.number, self.wavelength, self.incidence, self.intensity)
102        )

Represents an optical ray from geometrical optics. In order to optimize lookups, Rays are represented as dataclasses. The mandatory attributes are:

  • point: mgeo.Point
  • vector: mgeo.Vector
Ray( point: ARTcore.ModuleGeometry.Point, vector: ARTcore.ModuleGeometry.Vector, path: tuple = (0.0,), number: int = None, wavelength: float = None, incidence: float = None, intensity: float = None)
path: tuple
number: int
wavelength: float
incidence: float
intensity: float
def to_basis(self, r0, r, q):
35    def to_basis(self, r0, r, q):
36        """
37        Transforms the ray to the basis defined by the origin r0, the x-axis r and the y-axis q.
38        """
39        return Ray(
40            self.point.to_basis(r0, r, q),
41            self.vector.to_basis(r0, r, q),
42            self.path,
43            self.number,
44            self.wavelength,
45            self.incidence,
46            self.intensity,
47        )

Transforms the ray to the basis defined by the origin r0, the x-axis r and the y-axis q.

def from_basis(self, r0, r, q):
49    def from_basis(self, r0, r, q):
50        """
51        Transforms the ray from the basis defined by the origin r0, the x-axis r and the y-axis q.
52        """
53        return Ray(
54            self.point.from_basis(r0, r, q),
55            self.vector.from_basis(r0, r, q),
56            self.path,
57            self.number,
58            self.wavelength,
59            self.incidence,
60            self.intensity,
61        )

Transforms the ray from the basis defined by the origin r0, the x-axis r and the y-axis q.

def rotate(self, q):
63    def rotate(self, q):
64        """
65        Rotates the ray by the quaternion q.
66        """
67        return Ray(
68            self.point.rotate(q),
69            self.vector.rotate(q),
70            self.path,
71            self.number,
72            self.wavelength,
73            self.incidence,
74            self.intensity,
75        )

Rotates the ray by the quaternion q.

def translate(self, t):
77    def translate(self, t):
78        """
79        Rotates the ray by the vector t
80        """
81        return Ray(
82            self.point.translate(t),
83            self.vector,
84            self.path,
85            self.number,
86            self.wavelength,
87            self.incidence,
88            self.intensity,
89        )

Rotates the ray by the vector t

@dataclass(slots=True)
class RayList:
105@dataclass(slots=True)
106class RayList:
107    """
108    Class representing a list of rays in a form that is well suited to calculations.
109    Specifically, the points, vectors etc are all numpy arrays
110    It can then be converted to a list of Ray objects with the method 
111    """
112    point: mgeo.PointArray
113    vector: mgeo.VectorArray
114    path: np.ndarray
115    number: np.ndarray
116    wavelength: np.ndarray
117    incidence: np.ndarray
118    intensity: np.ndarray
119    N: int = None
120
121    @classmethod
122    def from_list(cls, rays):
123        N = len(rays)
124        if N == 0:
125            return cls(
126                mgeo.PointArray([[np.nan, np.nan, np.nan]]),
127                mgeo.VectorArray([[np.nan,np.nan,np.nan]]),
128                np.zeros((1,1)),
129                np.zeros(1),
130                np.zeros(1),
131                np.zeros(1),
132                np.zeros(1),
133                0,
134            )
135        N_r = len(rays[0].path)
136        points = np.zeros((N, 3))
137        vectors = np.zeros((N, 3))
138        paths = np.zeros((N, N_r))
139        numbers = np.zeros(N, dtype=int)
140        wavelengths = np.zeros(N)
141        incidences = np.zeros(N)
142        intensities = np.zeros(N)
143        for i, ray in enumerate(rays):
144            points[i:,] = ray.point
145            vectors[i:,] = ray.vector
146            paths[i:,] = ray.path
147            numbers[i] = ray.number
148            wavelengths[i] = ray.wavelength
149            incidences[i] = ray.incidence
150            intensities[i] = ray.intensity
151        return cls(
152            mgeo.PointArray(points),
153            mgeo.VectorArray(vectors),
154            paths,
155            numbers,
156            wavelengths,
157            incidences,
158            intensities,
159            len(rays),
160        )
161    
162    def __getitem__(self, key):
163        if isinstance(key, slice) or isinstance(key, list):
164            return RayList(
165                self.point[key],
166                self.vector[key],
167                self.path[key],
168                self.number[key],
169                self.wavelength[key],
170                self.incidence[key],
171                self.intensity[key],
172                len(key),
173            )
174        return Ray(
175            self.point[key],
176            self.vector[key],
177            tuple(self.path[key]),
178            self.number[key],
179            self.wavelength[key],
180            self.incidence[key],
181            self.intensity[key],
182        )
183    
184    def __len__(self):
185        return self.N
186    
187    def __iter__(self):
188        if self.N == 0:
189            return iter([])
190        return (Ray(point=self.point[i],
191                    vector=self.vector[i],
192                    path=tuple(self.path[i]),
193                    number=int(self.number[i]),
194                    wavelength=float(self.wavelength[i]),
195                    incidence=float(self.incidence[i]),
196                    intensity=float(self.intensity[i])) for i in range(self.N))
197
198    def to_basis(self, r0, r, q):
199        """
200        Transforms the ray to the basis defined by the origin r0, the x-axis r and the y-axis q.
201        """
202        return RayList(
203            self.point.to_basis(r0, r, q),
204            self.vector.to_basis(r0, r, q),
205            self.path,
206            self.number,
207            self.wavelength,
208            self.incidence,
209            self.intensity,
210            self.N
211        )
212    
213    def from_basis(self, r0, r, q):
214        """
215        Transforms the ray from the basis defined by the origin r0, the x-axis r and the y-axis q.
216        """
217        return RayList(
218            self.point.from_basis(r0, r, q),
219            self.vector.from_basis(r0, r, q),
220            self.path,
221            self.number,
222            self.wavelength,
223            self.incidence,
224            self.intensity,
225            self.N
226        )
227
228    def rotate(self, q):
229        """
230        Rotates the raylist by the quaternion q.
231        """
232        return RayList(
233            self.point.rotate(q),
234            self.vector.rotate(q),
235            self.path,
236            self.number,
237            self.wavelength,
238            self.incidence,
239            self.intensity,
240            self.N
241        )
242    
243    def translate(self, t):
244        """
245        Rotates the ray by the vector t
246        """
247        return RayList(
248            self.point.translate(t),
249            self.vector,
250            self.path,
251            self.number,
252            self.wavelength,
253            self.incidence,
254            self.intensity,
255            self.N
256        )
257
258    def __copy__(self):
259        """
260        Returns a new OpticalRay object with the same properties.
261        """
262        return RayList(
263            self.point.copy(),
264            self.vector.copy(),
265            self.path.copy(),
266            self.number.copy(),
267            self.wavelength.copy(),
268            self.incidence.copy(),
269            self.intensity.copy(),
270            self.N,
271        )
272
273    def __hash__(self):
274        points = self.point.reshape(1, -1)[0]
275        vectors = self.vector.reshape(1, -1)[0]
276        paths = self.path.reshape(1, -1)[0]
277        result = np.concatenate((points, vectors, paths, self.number, self.wavelength, self.incidence, self.intensity))
278        result = result[~np.isnan(result)]
279        result.flags.writeable = False
280        result_hash = hash(result.data.tobytes())
281        return result_hash
282
283    def __repr__(self) -> str:
284        intro = f"RayList with {self.N} rays\n"
285        average = mgeo.Vector(np.mean(self.vector)).normalized()
286        vectors = self.vector.normalized()
287        angles = np.arccos(np.clip(np.dot(vectors, average), -1.0, 1.0))
288        avg_string = "On average, the rays are oriented along the vector:\n" + str(average) + "\n"
289        NA = f"Numerical aperture: {np.max(np.sin(angles)):.3f}\n"
290        return intro +avg_string+ NA

Class representing a list of rays in a form that is well suited to calculations. Specifically, the points, vectors etc are all numpy arrays It can then be converted to a list of Ray objects with the method

RayList( point: ARTcore.ModuleGeometry.PointArray, vector: ARTcore.ModuleGeometry.VectorArray, path: numpy.ndarray, number: numpy.ndarray, wavelength: numpy.ndarray, incidence: numpy.ndarray, intensity: numpy.ndarray, N: int = None)
path: numpy.ndarray
number: numpy.ndarray
wavelength: numpy.ndarray
incidence: numpy.ndarray
intensity: numpy.ndarray
N: int
@classmethod
def from_list(cls, rays):
121    @classmethod
122    def from_list(cls, rays):
123        N = len(rays)
124        if N == 0:
125            return cls(
126                mgeo.PointArray([[np.nan, np.nan, np.nan]]),
127                mgeo.VectorArray([[np.nan,np.nan,np.nan]]),
128                np.zeros((1,1)),
129                np.zeros(1),
130                np.zeros(1),
131                np.zeros(1),
132                np.zeros(1),
133                0,
134            )
135        N_r = len(rays[0].path)
136        points = np.zeros((N, 3))
137        vectors = np.zeros((N, 3))
138        paths = np.zeros((N, N_r))
139        numbers = np.zeros(N, dtype=int)
140        wavelengths = np.zeros(N)
141        incidences = np.zeros(N)
142        intensities = np.zeros(N)
143        for i, ray in enumerate(rays):
144            points[i:,] = ray.point
145            vectors[i:,] = ray.vector
146            paths[i:,] = ray.path
147            numbers[i] = ray.number
148            wavelengths[i] = ray.wavelength
149            incidences[i] = ray.incidence
150            intensities[i] = ray.intensity
151        return cls(
152            mgeo.PointArray(points),
153            mgeo.VectorArray(vectors),
154            paths,
155            numbers,
156            wavelengths,
157            incidences,
158            intensities,
159            len(rays),
160        )
def to_basis(self, r0, r, q):
198    def to_basis(self, r0, r, q):
199        """
200        Transforms the ray to the basis defined by the origin r0, the x-axis r and the y-axis q.
201        """
202        return RayList(
203            self.point.to_basis(r0, r, q),
204            self.vector.to_basis(r0, r, q),
205            self.path,
206            self.number,
207            self.wavelength,
208            self.incidence,
209            self.intensity,
210            self.N
211        )

Transforms the ray to the basis defined by the origin r0, the x-axis r and the y-axis q.

def from_basis(self, r0, r, q):
213    def from_basis(self, r0, r, q):
214        """
215        Transforms the ray from the basis defined by the origin r0, the x-axis r and the y-axis q.
216        """
217        return RayList(
218            self.point.from_basis(r0, r, q),
219            self.vector.from_basis(r0, r, q),
220            self.path,
221            self.number,
222            self.wavelength,
223            self.incidence,
224            self.intensity,
225            self.N
226        )

Transforms the ray from the basis defined by the origin r0, the x-axis r and the y-axis q.

def rotate(self, q):
228    def rotate(self, q):
229        """
230        Rotates the raylist by the quaternion q.
231        """
232        return RayList(
233            self.point.rotate(q),
234            self.vector.rotate(q),
235            self.path,
236            self.number,
237            self.wavelength,
238            self.incidence,
239            self.intensity,
240            self.N
241        )

Rotates the raylist by the quaternion q.

def translate(self, t):
243    def translate(self, t):
244        """
245        Rotates the ray by the vector t
246        """
247        return RayList(
248            self.point.translate(t),
249            self.vector,
250            self.path,
251            self.number,
252            self.wavelength,
253            self.incidence,
254            self.intensity,
255            self.N
256        )

Rotates the ray by the vector t