Commit af6a38d5 authored by Andy Regensky's avatar Andy Regensky
Browse files

Extend calibration information, and improve sensor and lens configuration

parent ff4f753d
{
"sensor_height": 5.8,
"resolution": [1150, 1086],
"focal_length": 1.8,
"optical_center": [575.214839, 542.394470],
"coefficients": [
0.03145,
-0.24345,
......
......@@ -75,7 +75,6 @@ class BlockComparisonViewModel(QObject):
def updateBlocks(self):
self.threadpool.clear()
if (not self.mainViewModel.showBlockComparisonWindow or
not self.mainViewModel.sensor_dimensions_valid or
self.mainViewModel.currentFisheyeImage is None):
self.setTMCBlock(255 * np.ones((self.mainViewModel.blocksize[1],
self.mainViewModel.blocksize[0]),
......@@ -95,7 +94,7 @@ class BlockComparisonViewModel(QObject):
self.mainViewModel.perspective,
self.mainViewModel.origin,
self.mainViewModel.blocksize,
self.mainViewModel.center,
self.mainViewModel.optical_center,
self.mainViewModel.control_point_motion_vectors,
self.mainViewModel.motion_model,
None,
......@@ -112,7 +111,7 @@ class BlockComparisonViewModel(QObject):
self.mainViewModel.projection,
self.mainViewModel.origin,
self.mainViewModel.blocksize,
self.mainViewModel.center,
self.mainViewModel.optical_center,
self.mainViewModel.control_point_motion_vectors,
self.mainViewModel.motion_model,
self.mainViewModel.viewport,
......
......@@ -29,6 +29,8 @@
import json
import imageio
from PySide2.QtCore import QObject, Signal, QThreadPool, Slot
import numpy as np
from fishui import projections
......@@ -61,8 +63,9 @@ class MainViewModel(QObject):
'calibrated': projections.CalibratedProjection
}
self.sensor_size_mm = (5.2, 5.2)
self.sensor_height_mm = 5.2
self.sensor_size_px = (1088, 1088)
self.optical_center = (543.5, 543.5)
self.focal_length_mm = 1.8
self.projection_key = 'equisolid'
self.calibration_coeffs = None
......@@ -107,41 +110,32 @@ class MainViewModel(QObject):
self.showBlockComparisonWindow = show
self.viewStateDidChange.emit()
@property
def center(self):
return (np.asarray(self.sensor_size_px) - 1) / 2
@property
def sensor_dimensions_valid(self):
return np.isclose(self.sensor_size_px[0]/self.sensor_size_mm[0],
self.sensor_size_px[1]/self.sensor_size_mm[1])
@property
def focal_length_px(self):
return (self.sensor_size_px[0]/self.sensor_size_mm[0]) * self.focal_length_mm
return (self.sensor_size_px[0]/self.sensor_height_mm) * self.focal_length_mm
@property
def projection(self):
if self.projection_key == 'calibrated':
calib_coeffs = self.calibration_coeffs
projection = projections.CalibratedProjection(calib_coeffs,
self.sensor_size_mm, self.sensor_size_px,
self.sensor_size_px[0]/self.sensor_height_mm,
self.focal_length_mm,
self.center)
self.optical_center)
else:
projection = self.projections[self.projection_key](self.focal_length_px, self.center)
projection = self.projections[self.projection_key](self.focal_length_px, self.optical_center)
return projection
@property
def perspective(self):
return projections.PerspectiveProjection(self.focal_length_px, self.center)
return projections.PerspectiveProjection(self.focal_length_px, self.optical_center)
@property
def fov_180_radius(self):
return self.projection.radius(np.deg2rad(90))
def setSensorSize(self, sensor_size):
self.sensor_size_mm = sensor_size
def setSensorHeight(self, sensor_height):
self.sensor_height_mm = sensor_height
self.propertiesDidChange.emit()
self.updateBlockProjection()
self.updatePerspectiveImages()
......@@ -152,6 +146,12 @@ class MainViewModel(QObject):
self.updateBlockProjection()
self.updatePerspectiveImages()
def setOpticalCenter(self, optical_center):
self.optical_center = optical_center
self.propertiesDidChange.emit()
self.updateBlockProjection()
self.updatePerspectiveImages()
def setFocalLength(self, focal_length_mm):
self.focal_length_mm = focal_length_mm
self.propertiesDidChange.emit()
......@@ -160,7 +160,11 @@ class MainViewModel(QObject):
def setProjection(self, projection_key, calib_file=None):
if projection_key == 'calibrated' and calib_file is not None:
self.focal_length_mm, self.calibration_coeffs = self.readCalibrationData(calib_file)
(self.sensor_height_mm,
self.sensor_size_px,
self.focal_length_mm,
self.optical_center,
self.calibration_coeffs) = self.readCalibrationData(calib_file)
elif projection_key == 'calibrated' and calib_file is None:
raise Exception("Calibrated projection requires calibration file argument.")
elif projection_key != 'calibrated' and calib_file is not None:
......@@ -173,11 +177,14 @@ class MainViewModel(QObject):
def readCalibrationData(self, calib_file):
with open(calib_file, 'r') as file:
calib_json = json.load(file)
sensor_height = calib_json['sensor_height']
sensor_resolution = np.array([int(value) for value in calib_json['resolution']])
focal_length = calib_json['focal_length']
optical_center = np.array([float(value) for value in calib_json['optical_center']])
calib_coeffs = np.array([float(coeff) for coeff in calib_json['coefficients']])
if len(calib_coeffs) == 0:
raise ValueError
return focal_length, calib_coeffs
return sensor_height, sensor_resolution, focal_length, optical_center, calib_coeffs
def setOrigin(self, origin):
self.origin = origin
......@@ -212,14 +219,12 @@ class MainViewModel(QObject):
def updateBlockProjection(self):
self.bp_threadpool.clear()
if not self.sensor_dimensions_valid:
return
worker = BlockProjectionWorker(self.perspective,
self.projection,
self.samples_per_edge,
self.origin,
self.blocksize,
self.center,
self.optical_center,
self.control_point_motion_vectors,
self.motion_model,
self.viewport)
......@@ -247,6 +252,9 @@ class MainViewModel(QObject):
def setFisheyeImages(self, images):
self._fisheye_images = images
if self.projection_key != "calibrated" and len(self._fisheye_images) > 0:
shape = imageio.imread(images[0]).shape[:2]
self.setSensorResolution((shape[1], shape[0]))
self._current_idx = 0
self.updatePerspectiveImages()
self.imagesDidChange.emit()
......@@ -259,9 +267,13 @@ class MainViewModel(QObject):
self._current_idx -= 1
self.imagesDidChange.emit()
@property
def isFisheyeImageLoaded(self):
return len(self._fisheye_images) > 0
@property
def currentFisheyeImage(self):
if len(self._fisheye_images) == 0:
if not self.isFisheyeImageLoaded:
return None
return self._fisheye_images[self._current_idx % len(self._fisheye_images)]
......@@ -278,13 +290,11 @@ class MainViewModel(QObject):
def updatePerspectiveImages(self):
self.ip_threadpool.clear()
if not self.sensor_dimensions_valid:
return
self._perspective_images = {}
self.imagesDidChange.emit()
for image_file in self._fisheye_images:
for scale in range(1, 11):
worker = InterpolationWorker(image_file, scale, self.perspective, self.projection, self.center,
worker = InterpolationWorker(image_file, scale, self.perspective, self.projection, self.optical_center,
self.viewport)
worker.signals.didFinish.connect(self.interpolationDidFinish)
self.ip_threadpool.start(worker)
......
......@@ -41,10 +41,9 @@ class CalibratedProjection(RadialProjection):
def __init__(self,
polynom_coeffs: Union[np.ndarray, Tuple],
sensor_size_mm: Tuple[float, float],
sensor_size_px: Tuple[int, int],
px_per_mm: float,
focal_length_mm: float,
optical_center: Tuple[float, float] = None):
optical_center: Tuple[float, float]):
"""
Initialize a new calibrated projection (up to 200 degrees FOV).
......@@ -56,12 +55,8 @@ class CalibratedProjection(RadialProjection):
:param sensor_size_px: sensor size in pixels
:param focal_length_mm: focal length in mm
"""
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]
self._px_per_mm = px_per_mm
focal_length_px = self._px_per_mm * focal_length_mm
if optical_center is None:
optical_center = ((sensor_size_px[0] - 1) / 2, (sensor_size_px[1] - 1) / 2)
super().__init__(focal_length_px, optical_center)
# Get forward polynomial directly from calibrated coefficients.
......
......@@ -78,38 +78,11 @@ class DistortionVisualizationWidget(QWidget):
self.setLayout(layout)
# Invalid sensor dimensions overlay
self.overlay = QLabel(self)
self.overlay.setText("Sensor dimensions do not result in square-sized pixels.")
self.overlay.setStyleSheet("background-color: rgba(55, 55, 55, 0.5);"
"color: white;"
"font-weight: bold;"
"font-size: 16px;")
self.overlay.setAlignment(Qt.AlignCenter)
self.overlay.setVisible(False)
self.viewModelChanged()
self.show()
def resizeEvent(self, e):
w = e.size().width()
h = e.size().height()
self.overlay.setGeometry(0, 0, w, h)
@Slot()
def viewModelChanged(self):
if self.viewModel.sensor_dimensions_valid:
self.overlay.setVisible(False)
self.fisheyeLabel.setGraphicsEffect(None)
self.fisheyeARWidget.setGraphicsEffect(None)
self.perspectiveLabel.setGraphicsEffect(None)
self.perspectiveARWidget.setGraphicsEffect(None)
aspect_ratio = self.viewModel.sensor_size_px[0]/self.viewModel.sensor_size_px[1]
self.fisheyeARWidget.setAspectRatio(aspect_ratio)
self.perspectiveARWidget.setAspectRatio(aspect_ratio)
else:
self.overlay.setVisible(True)
self.fisheyeLabel.setGraphicsEffect(QGraphicsBlurEffect(self))
self.fisheyeARWidget.setGraphicsEffect(QGraphicsBlurEffect(self))
self.perspectiveLabel.setGraphicsEffect(QGraphicsBlurEffect(self))
self.perspectiveARWidget.setGraphicsEffect(QGraphicsBlurEffect(self))
aspect_ratio = self.viewModel.sensor_size_px[0]/self.viewModel.sensor_size_px[1]
self.fisheyeARWidget.setAspectRatio(aspect_ratio)
self.perspectiveARWidget.setAspectRatio(aspect_ratio)
......@@ -58,8 +58,7 @@ class FisheyeBackgroundWidget(QWidget):
painter.setRenderHint(QPainter.Antialiasing)
painter.fillRect(painter.window(), QColor(255, 255, 255))
self.drawImage(painter)
if self.viewModel.sensor_dimensions_valid:
self.drawFisheyeBoundary(painter)
self.drawFisheyeBoundary(painter)
painter.end()
def drawImage(self, painter):
......@@ -80,8 +79,8 @@ class FisheyeBackgroundWidget(QWidget):
painter.setPen(pen)
painter.drawEllipse(
QRect(
self.viewModel.center[0] - self.viewModel.fov_180_radius,
self.viewModel.center[1] - self.viewModel.fov_180_radius,
self.viewModel.optical_center[0] - self.viewModel.fov_180_radius,
self.viewModel.optical_center[1] - self.viewModel.fov_180_radius,
2 * self.viewModel.fov_180_radius,
2 * self.viewModel.fov_180_radius
)
......
......@@ -53,11 +53,7 @@ class PropertiesWidget(QWidget):
self.label.setText("Properties")
self.sensorSizeLabel = QLabel(self)
self.sensorSizeLabel.setText("Sensor size in mm")
self.sensorSizeWidthEdit = QLineEdit(self)
self.sensorSizeWidthEdit.setValidator(doubleValidator)
self.sensorSizeWidthEdit.editingFinished.connect(self.sensorSizeWidthEditingFinished)
self.sensorSizeLabel.setText("Sensor height in mm")
self.sensorSizeHeightEdit = QLineEdit(self)
self.sensorSizeHeightEdit.setValidator(doubleValidator)
......@@ -74,6 +70,17 @@ class PropertiesWidget(QWidget):
self.resolutionHeightEdit.setValidator(intValidator)
self.resolutionHeightEdit.editingFinished.connect(self.resolutionHeightEditingFinished)
self.opticalCenterLabel = QLabel(self)
self.opticalCenterLabel.setText("Optical center")
self.opticalCenterXEdit = QLineEdit(self)
self.opticalCenterXEdit.setValidator(doubleValidator)
self.opticalCenterXEdit.editingFinished.connect(self.opticalCenterXEditingFinished)
self.opticalCenterYEdit = QLineEdit(self)
self.opticalCenterYEdit.setValidator(doubleValidator)
self.opticalCenterYEdit.editingFinished.connect(self.opticalCenterYEditingFinished)
self.focalLengthLabel = QLabel(self)
self.focalLengthLabel.setText("Focal length in mm")
......@@ -172,67 +179,97 @@ class PropertiesWidget(QWidget):
layout.addWidget(self.label, 0, 0, 1, -1)
layout.addItem(QSpacerItem(10, 20), 1, 0)
layout.addWidget(self.sensorSizeLabel, 2, 0, 1, -1)
layout.addWidget(self.sensorSizeWidthEdit, 3, 0)
layout.addWidget(self.sensorSizeHeightEdit, 3, 1)
layout.addWidget(self.sensorSizeHeightEdit, 3, 0)
layout.addWidget(self.resolutionLabel, 4, 0, 1, -1)
layout.addWidget(self.resolutionWidthEdit, 5, 0)
layout.addWidget(self.resolutionHeightEdit, 5, 1)
layout.addWidget(self.focalLengthLabel, 6, 0)
layout.addWidget(self.projectionLabel, 6, 1)
layout.addWidget(self.focalLengthEdit, 7, 0)
layout.addWidget(self.projectionComboBox, 7, 1)
layout.addItem(QSpacerItem(10, 20), 8, 0)
layout.addWidget(self.originLabel, 9, 0, 1, -1)
layout.addWidget(self.originXEdit, 10, 0)
layout.addWidget(self.originYEdit, 10, 1)
layout.addWidget(self.blocksizeLabel, 11, 0, 1, -1)
layout.addWidget(self.blocksizeWidthEdit, 12, 0)
layout.addWidget(self.blocksizeHeightEdit, 12, 1)
layout.addItem(QSpacerItem(10, 20), 13, 0)
layout.addWidget(self.viewportLabel, 14, 0, 1, -1)
layout.addWidget(self.viewportComboBox, 15, 0, 1, -1)
layout.addWidget(self.motionModelLabel, 16, 0, 1, -1)
layout.addWidget(self.motionModelComboBox, 17, 0, 1, -1)
layout.addWidget(self.motionVector0Label, 18, 0, 1, -1)
layout.addWidget(self.motionVectorX0Edit, 19, 0)
layout.addWidget(self.motionVectorY0Edit, 19, 1)
layout.addWidget(self.motionVector1Label, 20, 0, 1, -1)
layout.addWidget(self.motionVectorX1Edit, 21, 0)
layout.addWidget(self.motionVectorY1Edit, 21, 1)
layout.addWidget(self.motionVector2Label, 22, 0, 1, -1)
layout.addWidget(self.motionVectorX2Edit, 23, 0)
layout.addWidget(self.motionVectorY2Edit, 23, 1)
layout.addItem(QSpacerItem(10, 20), 24, 0)
layout.addWidget(self.perspectiveScaleLabel, 25, 0, 1, -1)
layout.addWidget(self.perspectiveScaleSlider, 26, 0, 1, -1)
layout.addWidget(self.opticalCenterLabel, 6, 0, 1, -1)
layout.addWidget(self.opticalCenterXEdit, 7, 0)
layout.addWidget(self.opticalCenterYEdit, 7, 1)
layout.addWidget(self.focalLengthLabel, 8, 0)
layout.addWidget(self.projectionLabel, 8, 1)
layout.addWidget(self.focalLengthEdit, 9, 0)
layout.addWidget(self.projectionComboBox, 9, 1)
layout.addItem(QSpacerItem(10, 20), 10, 0)
layout.addWidget(self.originLabel, 11, 0, 1, -1)
layout.addWidget(self.originXEdit, 12, 0)
layout.addWidget(self.originYEdit, 12, 1)
layout.addWidget(self.blocksizeLabel, 13, 0, 1, -1)
layout.addWidget(self.blocksizeWidthEdit, 14, 0)
layout.addWidget(self.blocksizeHeightEdit, 14, 1)
layout.addItem(QSpacerItem(10, 20), 15, 0)
layout.addWidget(self.viewportLabel, 16, 0, 1, -1)
layout.addWidget(self.viewportComboBox, 17, 0, 1, -1)
layout.addWidget(self.motionModelLabel, 18, 0, 1, -1)
layout.addWidget(self.motionModelComboBox, 19, 0, 1, -1)
layout.addWidget(self.motionVector0Label, 20, 0, 1, -1)
layout.addWidget(self.motionVectorX0Edit, 21, 0)
layout.addWidget(self.motionVectorY0Edit, 21, 1)
layout.addWidget(self.motionVector1Label, 22, 0, 1, -1)
layout.addWidget(self.motionVectorX1Edit, 23, 0)
layout.addWidget(self.motionVectorY1Edit, 23, 1)
layout.addWidget(self.motionVector2Label, 24, 0, 1, -1)
layout.addWidget(self.motionVectorX2Edit, 25, 0)
layout.addWidget(self.motionVectorY2Edit, 25, 1)
layout.addItem(QSpacerItem(10, 20), 26, 0)
layout.addWidget(self.perspectiveScaleLabel, 27, 0, 1, -1)
layout.addWidget(self.perspectiveScaleSlider, 28, 0, 1, -1)
layout.setAlignment(Qt.AlignTop)
self.setLayout(layout)
self.show()
@staticmethod
def setTextEditStylesheet(textEdit):
textEdit.setStyleSheet(f"color: {'white' if textEdit.isEnabled() else 'gray'}")
@Slot()
def updateValues(self):
self.sensorSizeWidthEdit.setText(f"{self.viewModel.sensor_size_mm[0]}")
self.sensorSizeHeightEdit.setText(f"{self.viewModel.sensor_size_mm[1]}")
self.sensorSizeHeightEdit.setText(f"{self.viewModel.sensor_height_mm}")
self.sensorSizeHeightEdit.setEnabled(self.viewModel.projection_key != 'calibrated')
self.setTextEditStylesheet(self.sensorSizeHeightEdit)
self.resolutionWidthEdit.setText(f"{self.viewModel.sensor_size_px[0]}")
self.resolutionWidthEdit.setEnabled(not self.viewModel.isFisheyeImageLoaded and self.viewModel.projection_key != 'calibrated')
self.setTextEditStylesheet(self.resolutionWidthEdit)
self.resolutionHeightEdit.setText(f"{self.viewModel.sensor_size_px[1]}")
self.resolutionHeightEdit.setEnabled(not self.viewModel.isFisheyeImageLoaded and self.viewModel.projection_key != 'calibrated')
self.setTextEditStylesheet(self.resolutionHeightEdit)
self.opticalCenterXEdit.setText(f"{self.viewModel.optical_center[0]}")
self.opticalCenterXEdit.setEnabled(self.viewModel.projection_key != 'calibrated')
self.setTextEditStylesheet(self.opticalCenterXEdit)
self.opticalCenterYEdit.setText(f"{self.viewModel.optical_center[1]}")
self.opticalCenterYEdit.setEnabled(self.viewModel.projection_key != 'calibrated')
self.setTextEditStylesheet(self.opticalCenterYEdit)
self.focalLengthEdit.setText(f"{self.viewModel.focal_length_mm}")
self.focalLengthEdit.setEnabled(self.viewModel.projection_key != 'calibrated')
self.setTextEditStylesheet(self.focalLengthEdit)
self.projectionComboBox.setCurrentText(self.viewModel.projection_key)
self.originXEdit.setText(f"{self.viewModel.origin[0]}")
self.originYEdit.setText(f"{self.viewModel.origin[1]}")
self.blocksizeWidthEdit.setText(f"{self.viewModel.blocksize[0]}")
self.blocksizeHeightEdit.setText(f"{self.viewModel.blocksize[1]}")
self.viewportComboBox.setCurrentText(self.viewModel.viewport)
self.motionModelComboBox.setCurrentText(self.viewModel.motion_model)
self.motionVectorX0Edit.setText(f"{self.viewModel.control_point_motion_vectors[0, 0]}")
self.motionVectorY0Edit.setText(f"{self.viewModel.control_point_motion_vectors[0, 1]}")
self.motionVectorX1Edit.setText(f"{self.viewModel.control_point_motion_vectors[1, 0]}")
self.motionVectorY1Edit.setText(f"{self.viewModel.control_point_motion_vectors[1, 1]}")
self.motionVectorX2Edit.setText(f"{self.viewModel.control_point_motion_vectors[2, 0]}")
self.motionVectorY2Edit.setText(f"{self.viewModel.control_point_motion_vectors[2, 1]}")
self.perspectiveScaleLabel.setText(f"Perspective scale: {self.viewModel.perspective_scale}")
self.perspectiveScaleSlider.setValue(self.viewModel.perspective_scale)
if self.viewModel.motion_model == 'translatory':
self.motionVector0Label.setText("Motion Vector")
self.motionVector1Label.setHidden(True)
......@@ -258,17 +295,9 @@ class PropertiesWidget(QWidget):
self.motionVectorX2Edit.setHidden(False)
self.motionVectorY2Edit.setHidden(False)
@Slot()
def sensorSizeWidthEditingFinished(self):
sensor_size = self.viewModel.sensor_size_mm
sensor_size = (float(self.sensorSizeWidthEdit.text()), sensor_size[1])
self.viewModel.setSensorSize(sensor_size)
@Slot()
def sensorSizeHeightEditingFinished(self):
sensor_size = self.viewModel.sensor_size_mm
sensor_size = (sensor_size[0], float(self.sensorSizeHeightEdit.text()))
self.viewModel.setSensorSize(sensor_size)
self.viewModel.setSensorHeight(float(self.sensorSizeHeightEdit.text()))
@Slot()
def resolutionWidthEditingFinished(self):
......@@ -282,6 +311,18 @@ class PropertiesWidget(QWidget):
resolution = (resolution[0], int(self.resolutionHeightEdit.text()))
self.viewModel.setSensorResolution(resolution)
@Slot()
def opticalCenterXEditingFinished(self):
center = self.viewModel.optical_center
center = (float(self.opticalCenterXEdit.text()), center[1])
self.viewModel.setOpticalCenter(center)
@Slot()
def opticalCenterYEditingFinished(self):
center = self.viewModel.optical_center
center = (center[0], float(self.opticalCenterYEdit.text()))
self.viewModel.setOpticalCenter(center)
@Slot()
def focalLengthEditingFinished(self):
self.viewModel.setFocalLength(float(self.focalLengthEdit.text()))
......
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