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
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
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.
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.
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
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 )
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.
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.
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.
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