Source code for kerrpy.camera

from .universe import universe
from .raytracer import RayTracer
from .geodesics import Congruence, CongruenceSnapshot

from .utils.attr_dict import AttrDict

import numpy as np
from numpy import sin, cos, sqrt
from matplotlib import image as mimg


[docs]class Camera: """Pinhole camera placed near a Kerr black hole. This class contains the necessary data to define a camera that is located on the coordinate system of a Kerr black hole. Attributes: r (double): Distance to the coordinate origin; i.e., distance to the black hole centre. r2 (double): Square of `r`. theta (double): Inclination of the camera with respect to the black hole. phi (double): Azimuth of the camera with respect to the black hole. focalLength (double): Distance between the focal point (where every ray that reaches the camera has to pass through) and the focal plane (where the actual sensor/film is placed). sensorSize (tuple): 2-tuple that defines the physical dimensions of the sensor in the following way: `(Height, Width)`. sensorShape (tuple): 2-tuple that defines the number of pixels of the sensor in the following way: `(Number of rows, Number of columns)`. pixelWidth (double): Width of one single pixel in physical units. It is computed as `Number of columns / Sensor width`. pixelHeight (double): Height of one single pixel in physical units. It is computed as `Number of rows / Sensor height`. speed (double): Speed of the camera, that follows a circular orbit around the black hole in the equatorial plane. It is computed using the formula (A.7) of Thorne's paper. roll (double): The roll angle of the CCD; i.e., the rotation angle of the CCD on its plane. Defaults to zero, that means the CCD is facing the black hole centre. pitch (double): The pitch angle of the CCD; i.e., the above/below direction of looking. Defaults to zero, that means the CCD is facing the black hole centre. yaw (double): The yaw angle of the CCD; i.e., the left/right direction of lookin. Defaults to zero, that means the CCD is facing the black hole centre. """
[docs] def __init__(self, r, theta, phi, focalLength, sensorShape, sensorSize, roll=0, pitch=0, yaw=0): """Builds the camera defined by `focalLength`, `sensorShape` and `sensorSize` and locates it at the passed coordinates :math:`(r_c, \\theta_c, \\phi_c)` Args: r (double): Distance to the coordinate origin; i.e., distance to the black hole centre. theta (double): Inclination of the camera with respect to the black hole. phi (double): Azimuth of the camera with respect to the black hole. focalLength (double): Distance between the focal point (where every row that reaces the camera has to pass through) and the focal plane (where the actual sensor/film is placed). sensorShape (tuple): 2-tuple that defines the number of pixels of the sensor in the following way: `(Number of rows, Number of columns)`. sensorSize (tuple): 2-tuple that defines the physical dimensions of the sensor in the following way: `(Height, Width)`. roll (double): The roll angle of the CCD; i.e., the rotation angle of the CCD on the plane of the CCD. Defaults to zero, that means the CCD is facing the black hole centre. pitch (double): The pitch angle of the CCD; i.e., the above/below direction of looking. Defaults to zero, that means the CCD is facing the black hole centre. yaw (double): The yaw angle of the CCD; i.e., the left/right direction of lookin. Defaults to zero, that means the CCD is facing the black hole centre. """ # Define position self._r = r self.r2 = r * r self._theta = theta self._phi = phi # Define lens properties self.focalLength = focalLength # Define sensor properties self._sensorShape = sensorShape self._sensorSize = sensorSize # Define rotation of the CCD self.roll = roll self.pitch = pitch self.yaw = yaw # Compute the width and height of a pixel on physical units self.pixelWidth, self.pixelHeight = self._compute_pixel_size() # Compute every property for the camera: metric, speed, engine self._update() # Compile engine self.engine = RayTracer(self) # Add camera to the Universe universe.cameras.add(self)
[docs] def __del__(self): # Remove camera from the Universe universe.cameras.remove(self)
def _update(self): # Compute the value of the metric on the camera position self.metric = self._compute_metric_value() # Compute the speed self.speed = self._compute_speed() # Recompile ray tracer in the next photo self._reset = True def _compute_pixel_size(self): """Compute the width and height of a pixel, taking into account the physical measure of the sensor (sensorSize) and the number of pixels per row and per column on the sensor (sensorShape) """ # Shorten long named variables to ease the notation H, W = self.sensorSize[0], self.sensorSize[1] rows, cols = self.sensorShape[0], self.sensorShape[1] # Compute width and height of a pixel pixelWidth = np.float(W) / np.float(cols) pixelHeight = np.float(H) / np.float(rows) return pixelWidth, pixelHeight def _compute_metric_value(self): # Shorten long named variables to ease the notation a = universe.spin a2 = universe.spinSquared r = self.r r2 = self.r2 theta = self.theta # Compute the constants described between (A.1) and (A.2) ro = sqrt(r2 + a2 * cos(theta)**2) delta = r2 + a2 sigma = sqrt((r2 + a2)**2 - a2 * delta * sin(theta)**2) alpha = ro * sqrt(delta) / sigma omega = 2 * a * r / (sigma**2) # Wut? pomega? See https://en.wikipedia.org/wiki/Pi_(letter)#Variant_pi pomega = sigma * sin(theta) / ro return AttrDict(ro=ro, delta=delta, sigma=sigma, alpha=alpha, omega=omega, pomega=pomega) def _compute_speed(self): """Given a Kerr metric and a black hole, this method sets the speed of the camera at a circular orbit in the equatorial plane, following formula (A.7) of :cite:`thorne15`: .. math:: \\beta = \\frac{\\varpi}{\\alpha}(\\Omega - \\omega), where :math:`\\Omega = \\frac{1}{a + r_c^{3/2}}` and the other constants are the ones defined in the Kerr metric object. See :class:`~.KerrMetric`. Args: kerr (:class:`~.KerrMetric`): A :class:`~.KerrMetric` object containing the constants needed for the equations. blackHole (:class:`~.BlackHole`): A :class:`~.BlackHole` object containing the specifications of the black hole located a the coordinate origin. """ # Retrieve blackhole's spin and some Kerr constants a = universe.spin r = self.r pomega = self.metric.pomega omega = self.metric.omega alpha = self.metric.alpha # Define speed with equation (A.7) Omega = 1. / (a + r**(3. / 2.)) beta = pomega * (Omega - omega) / alpha # FIXME: This is being forced to zero only for testing purposes. # Remove this line if you want some real fancy images. beta = 0 return beta @property def r(self): return self._r @r.setter def r(self, newValue): # Set r coordinate and its square self._r = newValue self.r2 = self.r * self.r # Compute again the value of the metric on the camera position self._update() @property def theta(self): return self._theta @theta.setter def theta(self, newValue): # Set theta coordinate self._theta = newValue # Compute again the value of the metric on the camera position self._update() @property def phi(self): return self._phi @phi.setter def phi(self, newValue): # Set phi angle self._phi = newValue # Compute again the value of the metric on the camera position self._update() @property def sensorShape(self): return self._sensorShape @sensorShape.setter def sensorShape(self, newValue): # Set sensor shape self._sensorShape = newValue # Update the computation on pixel width and height self.pixelWidth, self.pixelHeight = self._compute_pixel_size() @property def sensorSize(self): return self._sensorSize @sensorSize.setter def sensorSize(self, newValue): # Set sensor shape self._sensorSize = newValue # Update the computation on pixel width and height self.pixelWidth, self.pixelHeight = self._compute_pixel_size()
[docs] def shoot(self, finalTime=-150, diskPath=None, spherePath=None, dashed_texture=False): self.engine = RayTracer(self) if self._reset: self.engine = RayTracer(self) self._reset = False raysStatus, raysCoordinates = self.engine.rayTrace(finalTime) texels = None if dashed_texture: texels = self.engine.dashed_textured_image() elif diskPath is not None and spherePath is not None: disk = mimg.imread(diskPath)[:, :, :3] sphere = mimg.imread(spherePath)[:, :, :3] texels = self.engine.texturedImage(disk.astype(np.float64), sphere.astype(np.float64)) return CongruenceSnapshot(raysStatus, raysCoordinates, texels), self.engine.totalTime
[docs] def slicedShoot(self, finalTime=-150, slicesNum=100): raysStatus, raysCoordinates = self.engine.slicedRayTrace(finalTime, slicesNum) return Congruence(raysStatus, raysCoordinates)