Commit 8386f85d authored by Andy Regensky's avatar Andy Regensky
Browse files

Simplify projections

parent 5d7cecad
......@@ -15,14 +15,14 @@ class MainViewModel(QObject):
self.sensor_size_px = (1088, 1088)
self.focal_length_mm = 1.8
self.origin = (256, 256)
self.origin = (512, 512)
self.blocksize = (64, 64)
self.affineModel = False
self.motion_vector_cp0 = (0, 0)
self.motion_vector_cp1 = (0, 0)
self.perspective_scale = 2
self.perspective_scale = 1
# Values used for drawing
self.samples_per_edge = 100
......@@ -82,8 +82,9 @@ class MainViewModel(QObject):
self.updateProjections()
def updateProjections(self):
self.projection = projections.EquisolidProjection.init_with(self.sensor_size_mm, self.sensor_size_px, self.focal_length_mm, fov=185)
self.perspective = projections.PerspectiveProjection(self.projection.focal_length_px, fov=185)
focal_length_px = projections.Projection.to_focal_length_px(self.focal_length_mm, self.sensor_size_mm, self.sensor_size_px)
self.projection = projections.EquisolidProjection(focal_length_px)
self.perspective = projections.PerspectiveProjection(focal_length_px)
self.blockSignals(True)
self.updateFisheyeBlockpoints()
self.updatePerspectiveBlockpoints()
......
......@@ -9,36 +9,25 @@ class CalibratedProjection(Projection):
Defines a calibrated projection model.
"""
# Note: We override __init__ despite advising against it in Projection.py
def __init__(self,
polynom_coeffs: Union[np.ndarray, Tuple],
sensor_size_mm: Tuple[float, float],
sensor_size_px: Tuple[int, int],
focal_length_mm: float,
fov: float):
focal_length_px: float):
"""
Initialize a new calibrated projection.
WE OVERRIDE INIT DESPITE ADVISING AGAINST IT IN Projection.py
:param polynom_coeffs: coefficients in decreasing powers of the
polynomial relating incident angle to sensor radius in mm
:param sensor_size_mm: sensor size in mm
:param sensor_size_px: sensor size in pixels
:param focal_length_mm: focal length in mm
:param fov: field of view in degrees
polynomial relating incident angle to sensor radius in px
:param focal_length_px: focal length in px
"""
if not np.isclose(sensor_size_px[0]/sensor_size_mm[0], sensor_size_px[1]/sensor_size_mm[1]):
raise Exception("Pixel shape must be square.")
self._px_per_mm = sensor_size_px[0]/sensor_size_mm[0]
focal_length_px = self._px_per_mm * focal_length_mm
super().__init__(focal_length_px, fov)
super().__init__(focal_length_px)
# Get forward polynomial directly from calibrated coefficients.
self._forward_poly = np.poly1d(polynom_coeffs)
# Get backward polynomial by matching a polynomial inversely to the
# results of the forward polynomial.
theta_help = np.linspace(0, np.deg2rad(fov/2), 10000)
theta_help = np.linspace(0, np.deg2rad(180), 10000)
radius_help = self._forward_poly(theta_help)
warnings.filterwarnings("ignore", category=np.RankWarning) # Filter rank warnings.
backward_coeffs = np.polyfit(radius_help, theta_help, (len(polynom_coeffs) - 1) * 2)
......@@ -46,7 +35,7 @@ class CalibratedProjection(Projection):
self._backward_poly = np.poly1d(backward_coeffs)
def radius(self, theta: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
return self._px_per_mm * self._forward_poly(theta)
return self._forward_poly(theta)
def theta(self, radius: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
return self._backward_poly(radius / self._px_per_mm)
return self._backward_poly(radius)
......@@ -8,21 +8,6 @@ class EquidistantProjection(Projection):
Defines the equidistant projection model.
"""
@classmethod
def init_with(cls, sensor_size_mm: Tuple[float, float], sensor_size_px: Tuple[int, int], focal_length_mm: float, fov: float):
"""
Specify new equidistant projection model with
:param sensor_size_mm: sensor size in mm [height, width]
:param sensor_size_px: sensor size in pixels [height, width]
:param focal_length_mm: focal length in mm
:param fov: field of view in degrees
"""
if sensor_size_px[0]/sensor_size_mm[0] != sensor_size_px[1]/sensor_size_mm[1]:
raise Exception("Pixel shape must be square.")
focal_length_px = sensor_size_px[0]/sensor_size_mm[0] * focal_length_mm
return cls(focal_length_px, fov)
def radius(self, theta: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
return self.focal_length_px * theta
......
......@@ -8,21 +8,6 @@ class EquisolidProjection(Projection):
Defines the equisolid projection model.
"""
@classmethod
def init_with(cls, sensor_size_mm: Tuple[float, float], sensor_size_px: Tuple[int, int], focal_length_mm: float, fov: float):
"""
Specify new equisolid projection model with
:param sensor_size_mm: sensor size in mm [height, width]
:param sensor_size_px: sensor size in pixels [height, width]
:param focal_length_mm: focal length in mm
:param fov: field of view in degrees
"""
if sensor_size_px[0]/sensor_size_mm[0] != sensor_size_px[1]/sensor_size_mm[1]:
raise Exception("Pixel shape must be square.")
focal_length_px = sensor_size_px[0]/sensor_size_mm[0] * focal_length_mm
return cls(focal_length_px, fov)
def radius(self, theta: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
return 2 * self.focal_length_px * np.sin(theta / 2)
......
......@@ -8,21 +8,6 @@ class OrthographicProjection(Projection):
Defines the orthographic projection model.
"""
@classmethod
def init_with(cls, sensor_size_mm: Tuple[float, float], sensor_size_px: Tuple[int, int], focal_length_mm: float, fov: float):
"""
Specify new orthographic projection model with
:param sensor_size_mm: sensor size in mm [height, width]
:param sensor_size_px: sensor size in pixels [height, width]
:param focal_length_mm: focal length in mm
:param fov: field of view in degrees
"""
if sensor_size_px[0]/sensor_size_mm[0] != sensor_size_px[1]/sensor_size_mm[1]:
raise Exception("Pixel shape must be square.")
focal_length_px = sensor_size_px[0]/sensor_size_mm[0] * focal_length_mm
return cls(focal_length_px, fov)
def radius(self, theta: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
return self.focal_length_px * np.sin(theta)
......
......@@ -8,21 +8,6 @@ class PerspectiveProjection(Projection):
Defines the perspective projection model.
"""
@classmethod
def init_with(cls, sensor_size_mm: Tuple[float, float], sensor_size_px: Tuple[int, int], focal_length_mm: float, fov: float):
"""
Specify new perspective projection model with
:param sensor_size_mm: sensor size in mm [height, width]
:param sensor_size_px: sensor size in pixels [height, width]
:param focal_length_mm: focal length in mm
:param fov: field of view in degrees
"""
if sensor_size_px[0]/sensor_size_mm[0] != sensor_size_px[1]/sensor_size_mm[1]:
raise Exception("Pixel shape must be square.")
focal_length_px = sensor_size_px[0]/sensor_size_mm[0] * focal_length_mm
return cls(focal_length_px, fov)
def radius(self, theta: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
return self.focal_length_px * np.tan(theta)
......
from abc import ABC, abstractmethod
from typing import Union
from typing import Union, Tuple, Optional
import numpy as np
......@@ -8,31 +8,40 @@ class Projection(ABC):
Provides forward and backward projection methods.
"""
def __init__(self, focal_length_px, fov: float):
def __init__(self, focal_length_px):
"""
Initialize a new Projection.
SUBCLASSES MAY NOT OVERRIDE THIS.
:param focal_length_px: focal length in pixels
:param fov: field of view in degrees
"""
self._focal_length_px = focal_length_px
self._fov = fov
self.focal_length_px = focal_length_px
@property
def focal_length_px(self):
@staticmethod
def to_focal_length_px(focal_length_mm: float,
sensor_size_mm: Optional[Tuple[float, float]] = None,
sensor_size_px: Optional[Tuple[int, int]] = None,
px_per_mm: Optional[float] = None):
"""
Get the focal length in pixels.
"""
return self._focal_length_px
Convert a focal length in mm to a focal length in px. Specify either
sensor dimensions or px_per_mm factor.
@property
def fov(self):
"""
Get the field of view.
:param focal_length_mm: focal length in mm
:param sensor_size_mm: sensor size in mm (height, width)
:param sensor_size_px: sensor size in px (height, width)
:param px_per_mm: pixels per mm
:return: focal length in px
"""
return self._fov
if px_per_mm is not None and (sensor_size_mm is not None or sensor_size_px is not None):
raise Exception("Invalid arguments: px_per_mm and (sensor_size_mm, sensor_size_px) "
"can not be given simultaneously.")
if sensor_size_mm is not None and sensor_size_px is not None:
if not np.isclose(sensor_size_px[0]/sensor_size_mm[0], sensor_size_px[1]/sensor_size_mm[1]):
raise Exception("Pixel height and width in mm must match.")
px_per_mm = sensor_size_px[0] / sensor_size_mm[0]
focal_length_px = focal_length_mm * px_per_mm
return focal_length_px
@abstractmethod
def radius(self, theta: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
......
......@@ -8,21 +8,6 @@ class StereographicProjection(Projection):
Defines the stereographic projection model.
"""
@classmethod
def init_with(cls, sensor_size_mm: Tuple[float, float], sensor_size_px: Tuple[int, int], focal_length_mm: float, fov: float):
"""
Specify new stereographic projection model with
:param sensor_size_mm: sensor size in mm [height, width]
:param sensor_size_px: sensor size in pixels [height, width]
:param focal_length_mm: focal length in mm
:param fov: field of view in degrees
"""
if sensor_size_px[0]/sensor_size_mm[0] != sensor_size_px[1]/sensor_size_mm[1]:
raise Exception("Pixel shape must be square.")
focal_length_px = sensor_size_px[0]/sensor_size_mm[0] * focal_length_mm
return cls(focal_length_px, fov)
def radius(self, theta: Union[float, np.ndarray]) -> Union[float, np.ndarray]:
return 2 * self.focal_length_px * np.tan(theta / 2)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment