Commit 07c3ccfd authored by Andy Regensky's avatar Andy Regensky
Browse files

Refactor motion model management and add 6-parameter affine motion model

parent 713c6dc8
......@@ -34,6 +34,7 @@ from PySide2.QtCore import QObject, Signal, QThreadPool, Slot
import numpy as np
from fishui import projections
from fishui.workers import BlockProjectionWorker, InterpolationWorker
from fishui.model import motion_models
class MainViewModel(QObject):
......@@ -69,9 +70,8 @@ class MainViewModel(QObject):
self.origin = (512, 512)
self.blocksize = (64, 64)
self.affineModel = False
self.motion_vector_cp0 = (0, 0)
self.motion_vector_cp1 = (0, 0)
self.motion_model = next(iter(motion_models.keys())) # First motion model in dict
self.control_point_motion_vectors = np.zeros((3, 2), dtype=int)
self.perspective_scale = 1
......@@ -179,26 +179,14 @@ class MainViewModel(QObject):
self.propertiesDidChange.emit()
self.updateBlockProjection()
def setAffineModel(self, enabled):
if not self.affineModel and enabled:
self.motion_vector_cp1 = self.motion_vector_cp0
self.affineModel = enabled
def setMotionModel(self, motion_model):
self.motion_model = motion_model
self.control_point_motion_vectors[1:, :] = self.control_point_motion_vectors[0, :]
self.propertiesDidChange.emit()
self.updateBlockProjection()
def setMotionVectorCP0(self, motion_vector):
self.motion_vector_cp0 = motion_vector
self.propertiesDidChange.emit()
self.updateBlockProjection()
def setMotionVectorCP1(self, motion_vector):
self.motion_vector_cp1 = motion_vector
self.propertiesDidChange.emit()
self.updateBlockProjection()
def setMotionVectors(self, motion_vector_cp0, motion_vector_cp1):
self.motion_vector_cp0 = motion_vector_cp0
self.motion_vector_cp1 = motion_vector_cp1
def setControlPointMotionVectors(self, control_point_motion_vectors):
self.control_point_motion_vectors = control_point_motion_vectors
self.propertiesDidChange.emit()
self.updateBlockProjection()
......@@ -217,9 +205,8 @@ class MainViewModel(QObject):
self.origin,
self.blocksize,
self.center,
self.motion_vector_cp0,
self.motion_vector_cp1,
self.affineModel)
self.control_point_motion_vectors,
self.motion_model)
worker.signals.didFinish.connect(self.blockProjectionDidFinish)
self.bp_threadpool.start(worker)
......
from abc import ABC, abstractmethod
from typing import Tuple
from enum import Enum
import numpy as np
class MotionModelBase(ABC):
def __init__(self, blocksize):
self.blocksize = blocksize
@abstractmethod
def perform_motion(self,
y: np.ndarray,
x: np.ndarray,
control_point_0: Tuple[float, float],
control_point_motion_vectors: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
pass
class TranslatoryModel(MotionModelBase):
def perform_motion(self,
y: np.ndarray,
x: np.ndarray,
control_point_0: Tuple[float, float],
control_point_motion_vectors: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
xm = x + control_point_motion_vectors[0, 0]
ym = y + control_point_motion_vectors[0, 1]
return ym, xm
class Affine4Model(MotionModelBase):
def perform_motion(self,
y: np.ndarray,
x: np.ndarray,
control_point_0: Tuple[float, float],
control_point_motion_vectors: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
x_cp0 = x - control_point_0[0]
y_cp0 = y - control_point_0[1]
xm = np.empty_like(x)
ym = np.empty_like(y)
for i in range(len(ym)):
n = x_cp0[i] / (self.blocksize[0] - 1)
m = y_cp0[i] / (self.blocksize[1] - 1)
affine_matrix = np.array([[1 - n, n, m, -m],
[-m, m, 1 - n, n]])
mv = affine_matrix.dot(control_point_motion_vectors[:2, :].T.reshape(-1))
xm[i] = x[i] + mv[0]
ym[i] = y[i] + mv[1]
return ym, xm
class Affine6Model(MotionModelBase):
def perform_motion(self,
y: np.ndarray,
x: np.ndarray,
control_point_0: Tuple[float, float],
control_point_motion_vectors: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
x_cp0 = x - control_point_0[0]
y_cp0 = y - control_point_0[1]
ym = np.empty_like(y)
xm = np.empty_like(x)
for i in range(len(ym)):
x_w = x_cp0[i] / (self.blocksize[0] - 1)
y_h = y_cp0[i] / (self.blocksize[1] - 1)
affine_vec = np.array([-x_w - y_h + 1, x_w, y_h])
mv = affine_vec.dot(control_point_motion_vectors)
xm[i] = x[i] + mv[0]
ym[i] = y[i] + mv[1]
return ym, xm
motion_models = {
'translatory': TranslatoryModel,
'affine4': Affine4Model,
'affine6': Affine6Model
}
from .MotionModel import motion_models
......@@ -52,8 +52,7 @@ class PerspectiveBlockWidget(QWidget):
self.controlPointMovedPolygon = None
self.updatePolygon(update=False)
self.initialMV0 = None
self.initialMV1 = None
self.initialCPMVs = None
self.initialMouse = None
# self.setMinimumSize(QSize(300, 300))
......@@ -78,7 +77,7 @@ class PerspectiveBlockWidget(QWidget):
perspectiveMovedPolygon.append(QPointF(block_xpm[idx], block_ypm[idx]))
self.perspectivePolygon = perspectivePolygon
self.perspectiveMovedPolygon = perspectiveMovedPolygon
if self.viewModel.affineModel:
if self.viewModel.motion_model != 'translatory':
if self.viewModel.block_ctr_px is None:
return
ctr_px = self.viewModel.block_ctr_px + sensor_offset_x
......@@ -103,7 +102,7 @@ class PerspectiveBlockWidget(QWidget):
self.viewModel.perspective_scale * self.viewModel.sensor_size_px[1]))
painter.setRenderHint(QPainter.Antialiasing)
self.drawPolygon(event, painter)
if self.viewModel.affineModel:
if self.viewModel.motion_model != 'translatory':
self.drawControlPointBlock(event, painter)
painter.end()
......@@ -136,8 +135,7 @@ class PerspectiveBlockWidget(QWidget):
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.initialMV0 = self.viewModel.motion_vector_cp0
self.initialMV1 = self.viewModel.motion_vector_cp1
self.initialCPMVs = self.viewModel.control_point_motion_vectors
self.initialMouse = event.pos()
def mouseMoveEvent(self, event):
......@@ -145,18 +143,14 @@ class PerspectiveBlockWidget(QWidget):
relativeMotion = event.pos() - self.initialMouse
relativeMotion.setX(relativeMotion.x() * (self.viewModel.perspective_scale * self.viewModel.sensor_size_px[0] / self.width()))
relativeMotion.setY(relativeMotion.y() * (self.viewModel.perspective_scale * self.viewModel.sensor_size_px[1] / self.height()))
if self.viewModel.affineModel:
motion_vector_cp0 = (self.initialMV0[0] + relativeMotion.x(), self.initialMV0[1] + relativeMotion.y())
motion_vector_cp1 = (self.initialMV1[0] + relativeMotion.x(), self.initialMV1[1] + relativeMotion.y())
self.viewModel.setMotionVectors(motion_vector_cp0, motion_vector_cp1)
else:
motion_vector = (self.initialMV0[0] + relativeMotion.x(), self.initialMV0[1] + relativeMotion.y())
self.viewModel.setMotionVectorCP0(motion_vector)
control_point_motion_vectors = self.initialCPMVs.copy()
control_point_motion_vectors[:, 0] += relativeMotion.x()
control_point_motion_vectors[:, 1] += relativeMotion.y()
self.viewModel.setControlPointMotionVectors(control_point_motion_vectors)
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.initialMV0 = None
self.initialMV1 = None
self.initialCPMVs = None
self.initialMouse = None
def wheelEvent(self, event):
......
......@@ -29,9 +29,9 @@
# THE POSSIBILITY OF SUCH DAMAGE.
from PySide2.QtWidgets import QWidget, QGridLayout, QLabel, QGraphicsBlurEffect, QSizePolicy, QFileDialog, QMenu, QAction
from PySide2.QtWidgets import QWidget, QFileDialog, QMenu, QAction
from PySide2.QtGui import QImage, QPainter
from PySide2.QtCore import Qt, Slot, QSize, QPoint
from PySide2.QtCore import Slot, QSize, QPoint
from fishui.MainViewModel import MainViewModel
from fishui.widgets.PerspectiveBackgroundWidget import PerspectiveBackgroundWidget
from fishui.widgets.PerspectiveBlockWidget import PerspectiveBlockWidget
......
......@@ -34,7 +34,7 @@ from PySide2.QtWidgets import (QWidget, QLabel, QLineEdit, QGridLayout, QSpacerI
QCheckBox, QComboBox, QFileDialog)
from PySide2.QtCore import Slot, Qt
from fishui.MainViewModel import MainViewModel
import fishui.projections as projections
from fishui.model import motion_models
class PropertiesWidget(QWidget):
......@@ -111,10 +111,9 @@ class PropertiesWidget(QWidget):
self.blocksizeHeightEdit.setValidator(intValidator)
self.blocksizeHeightEdit.editingFinished.connect(self.blocksizeHeightEditingFinished)
self.affineModelCheckbox = QCheckBox(self)
self.affineModelCheckbox.setText("Affine 4-parameter model")
self.affineModelCheckbox.setCheckState(Qt.Checked if self.viewModel.affineModel else Qt.Unchecked)
self.affineModelCheckbox.stateChanged.connect(self.affineModelCheckboxStateChanged)
self.motionModelComboBox = QComboBox(self)
self.motionModelComboBox.addItems(list(motion_models.keys()))
self.motionModelComboBox.currentTextChanged.connect(self.motionModelComboBoxTextChanged)
self.motionVector0Label = QLabel(self)
self.motionVector0Label.setText("Motion Vector:")
......@@ -138,6 +137,17 @@ class PropertiesWidget(QWidget):
self.motionVectorY1Edit.setValidator(negIntValidator)
self.motionVectorY1Edit.editingFinished.connect(self.motionVectorY1EditingFinished)
self.motionVector2Label = QLabel(self)
self.motionVector2Label.setText("CP2 Motion Vector:")
self.motionVectorX2Edit = QLineEdit(self)
self.motionVectorX2Edit.setValidator(negIntValidator)
self.motionVectorX2Edit.editingFinished.connect(self.motionVectorX2EditingFinished)
self.motionVectorY2Edit = QLineEdit(self)
self.motionVectorY2Edit.setValidator(negIntValidator)
self.motionVectorY2Edit.editingFinished.connect(self.motionVectorY2EditingFinished)
self.perspectiveScaleLabel = QLabel(self)
self.perspectiveScaleSlider = QSlider(Qt.Horizontal, self)
......@@ -170,16 +180,19 @@ class PropertiesWidget(QWidget):
layout.addWidget(self.blocksizeWidthEdit, 12, 0)
layout.addWidget(self.blocksizeHeightEdit, 12, 1)
layout.addItem(QSpacerItem(10, 20), 13, 0)
layout.addWidget(self.affineModelCheckbox, 14, 0, 1, -1)
layout.addWidget(self.motionModelComboBox, 14, 0, 1, -1)
layout.addWidget(self.motionVector0Label, 15, 0, 1, -1)
layout.addWidget(self.motionVectorX0Edit, 16, 0)
layout.addWidget(self.motionVectorY0Edit, 16, 1)
layout.addWidget(self.motionVector1Label, 17, 0, 1, -1)
layout.addWidget(self.motionVectorX1Edit, 18, 0)
layout.addWidget(self.motionVectorY1Edit, 18, 1)
layout.addItem(QSpacerItem(10, 20), 19, 0)
layout.addWidget(self.perspectiveScaleLabel, 20, 0, 1, -1)
layout.addWidget(self.perspectiveScaleSlider, 21, 0, 1, -1)
layout.addWidget(self.motionVector2Label, 19, 0, 1, -1)
layout.addWidget(self.motionVectorX2Edit, 20, 0)
layout.addWidget(self.motionVectorY2Edit, 20, 1)
layout.addItem(QSpacerItem(10, 20), 21, 0)
layout.addWidget(self.perspectiveScaleLabel, 22, 0, 1, -1)
layout.addWidget(self.perspectiveScaleSlider, 23, 0, 1, -1)
layout.setAlignment(Qt.AlignTop)
self.setLayout(layout)
......@@ -198,22 +211,38 @@ class PropertiesWidget(QWidget):
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.motionVectorX0Edit.setText(f"{self.viewModel.motion_vector_cp0[0]}")
self.motionVectorY0Edit.setText(f"{self.viewModel.motion_vector_cp0[1]}")
self.motionVectorX1Edit.setText(f"{self.viewModel.motion_vector_cp1[0]}")
self.motionVectorY1Edit.setText(f"{self.viewModel.motion_vector_cp1[1]}")
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.affineModel:
if self.viewModel.motion_model == 'translatory':
self.motionVector0Label.setText("Motion Vector:")
self.motionVector1Label.setHidden(True)
self.motionVectorX1Edit.setHidden(True)
self.motionVectorY1Edit.setHidden(True)
self.motionVector2Label.setHidden(True)
self.motionVectorX2Edit.setHidden(True)
self.motionVectorY2Edit.setHidden(True)
elif self.viewModel.motion_model == 'affine4':
self.motionVector0Label.setText("CP0 Motion Vector:")
self.motionVector1Label.setHidden(False)
self.motionVectorX1Edit.setHidden(False)
self.motionVectorY1Edit.setHidden(False)
self.motionVector2Label.setHidden(True)
self.motionVectorX2Edit.setHidden(True)
self.motionVectorY2Edit.setHidden(True)
else:
self.motionVector0Label.setText("Motion Vector:")
self.motionVector1Label.setHidden(True)
self.motionVectorX1Edit.setHidden(True)
self.motionVectorY1Edit.setHidden(True)
self.motionVector0Label.setText("CP0 Motion Vector")
self.motionVector1Label.setHidden(False)
self.motionVectorX1Edit.setHidden(False)
self.motionVectorY1Edit.setHidden(False)
self.motionVector2Label.setHidden(False)
self.motionVectorX2Edit.setHidden(False)
self.motionVectorY2Edit.setHidden(False)
@Slot()
def sensorSizeWidthEditingFinished(self):
......@@ -282,33 +311,45 @@ class PropertiesWidget(QWidget):
self.viewModel.setBlocksize(blocksize)
@Slot()
def affineModelCheckboxStateChanged(self):
enable = self.affineModelCheckbox.checkState() == Qt.Checked
self.viewModel.setAffineModel(enable)
def motionModelComboBoxTextChanged(self):
motion_model = self.motionModelComboBox.currentText()
self.viewModel.setMotionModel(motion_model)
@Slot()
def motionVectorX0EditingFinished(self):
motion_vector = self.viewModel.motion_vector_cp0
motion_vector = (int(self.motionVectorX0Edit.text()), motion_vector[1])
self.viewModel.setMotionVectorCP0(motion_vector)
control_point_motion_vectors = self.viewModel.control_point_motion_vectors.copy()
control_point_motion_vectors[0, 0] = int(self.motionVectorX0Edit.text())
self.viewModel.setControlPointMotionVectors(control_point_motion_vectors)
@Slot()
def motionVectorY0EditingFinished(self):
motion_vector = self.viewModel.motion_vector_cp0
motion_vector = (motion_vector[0], int(self.motionVectorY0Edit.text()))
self.viewModel.setMotionVectorCP0(motion_vector)
control_point_motion_vectors = self.viewModel.control_point_motion_vectors.copy()
control_point_motion_vectors[0, 1] = int(self.motionVectorY0Edit.text())
self.viewModel.setControlPointMotionVectors(control_point_motion_vectors)
@Slot()
def motionVectorX1EditingFinished(self):
motion_vector = self.viewModel.motion_vector_cp1
motion_vector = (int(self.motionVectorX1Edit.text()), motion_vector[1])
self.viewModel.setMotionVectorCP1(motion_vector)
control_point_motion_vectors = self.viewModel.control_point_motion_vectors.copy()
control_point_motion_vectors[1, 0] = int(self.motionVectorX1Edit.text())
self.viewModel.setControlPointMotionVectors(control_point_motion_vectors)
@Slot()
def motionVectorY1EditingFinished(self):
motion_vector = self.viewModel.motion_vector_cp1
motion_vector = (motion_vector[0], int(self.motionVectorY1Edit.text()))
self.viewModel.setMotionVectorCP1(motion_vector)
control_point_motion_vectors = self.viewModel.control_point_motion_vectors.copy()
control_point_motion_vectors[1, 1] = int(self.motionVectorY1Edit.text())
self.viewModel.setControlPointMotionVectors(control_point_motion_vectors)
@Slot()
def motionVectorX2EditingFinished(self):
control_point_motion_vectors = self.viewModel.control_point_motion_vectors.copy()
control_point_motion_vectors[2, 0] = int(self.motionVectorX2Edit.text())
self.viewModel.setControlPointMotionVectors(control_point_motion_vectors)
@Slot()
def motionVectorY2EditingFinished(self):
control_point_motion_vectors = self.viewModel.control_point_motion_vectors.copy()
control_point_motion_vectors[2, 1] = int(self.motionVectorY2Edit.text())
self.viewModel.setControlPointMotionVectors(control_point_motion_vectors)
@Slot()
def perspectiveScaleValueChanged(self):
......
......@@ -31,6 +31,7 @@
from PySide2.QtCore import QObject, QRunnable, Signal, Slot, QMutex, QMutexLocker
from fishui import coordinate_conversion
from fishui.model import motion_models
import numpy as np
......@@ -64,9 +65,8 @@ class BlockProjectionWorker(QRunnable):
origin,
blocksize,
center,
motion_vector_cp0,
motion_vector_cp1,
affineModel):
control_point_motion_vectors,
motion_model):
super().__init__()
self.perspective = perspective
self.projection = projection
......@@ -74,9 +74,8 @@ class BlockProjectionWorker(QRunnable):
self.origin = origin
self.blocksize = blocksize
self.center = center
self.motion_vector_cp0 = motion_vector_cp0
self.motion_vector_cp1 = motion_vector_cp1
self.affineModel = affineModel
self.control_point_motion_vectors = control_point_motion_vectors
self.motion_model = motion_models[motion_model](blocksize)
self.signals = BlockProjectionWorkerSignals()
......@@ -160,12 +159,19 @@ class BlockProjectionWorker(QRunnable):
self.block_xp = block_xpc
def updatePerspectiveMovedBlockpoints(self):
if self.affineModel:
self.updateControlBlockpoints()
self.updatePerspectiveMovedBlockpointsAffine()
else:
self.block_ypm = self.block_yp + self.motion_vector_cp0[1] * self.uwc_sign
self.block_xpm = self.block_xp + self.motion_vector_cp0[0] * self.uwc_sign
self.updateControlBlockpoints()
self.block_ypm, self.block_xpm = self.motion_model.perform_motion(
self.block_yp,
self.block_xp,
(self.block_ctr_px[0], self.block_ctr_py[0]),
self.control_point_motion_vectors
)
self.block_ctr_pym, self.block_ctr_pxm = self.motion_model.perform_motion(
self.block_ctr_py,
self.block_ctr_px,
(self.block_ctr_px[0], self.block_ctr_py[0]),
self.control_point_motion_vectors
)
def updateReprojectedBlockpoints(self):
r_pm, phi_m = coordinate_conversion.cartesian_to_polar(self.block_ypm, self.block_xpm)
......@@ -193,41 +199,6 @@ class BlockProjectionWorker(QRunnable):
cp0_y + self.blocksize[1],
cp0_y + self.blocksize[1]])
def updatePerspectiveMovedBlockpointsAffine(self):
cp0_position = (self.block_ctr_py[0], self.block_ctr_px[0])
cps_motion_vector = np.array([self.motion_vector_cp0[0], self.motion_vector_cp1[0],
self.motion_vector_cp0[1], self.motion_vector_cp1[1]])
block_yp_cp0 = self.block_yp - cp0_position[0]
block_xp_cp0 = self.block_xp - cp0_position[1]
block_ypm = np.empty_like(self.block_yp)
block_xpm = np.empty_like(self.block_xp)
for i in range(len(self.block_xp)):
m = block_yp_cp0[i] / (self.blocksize[0] - 1)
n = block_xp_cp0[i] / (self.blocksize[1] - 1)
affine_matrix = np.array([[-m, m, 1 - n, n],
[1 - n, n, m, -m]])
mv = affine_matrix.dot(cps_motion_vector)
block_ypm[i] = self.block_yp[i] + mv[0]
block_xpm[i] = self.block_xp[i] + mv[1]
block_ctr_pxm = np.empty(4)
block_ctr_pym = np.empty(4)
for i in range(4):
m = (self.block_ctr_py[i] - cp0_position[0]) / (self.blocksize[0] - 1)
n = (self.block_ctr_px[i] - cp0_position[1]) / (self.blocksize[1] - 1)
affine_matrix = np.array([[-m, m, 1 - n, n],
[1 - n, n, m, -m]])
mv = affine_matrix.dot(cps_motion_vector)
block_ctr_pym[i] = self.block_ctr_py[i] + mv[0]
block_ctr_pxm[i] = self.block_ctr_px[i] + mv[1]
self.block_ypm = block_ypm
self.block_xpm = block_xpm
self.block_ctr_pym = block_ctr_pym
self.block_ctr_pxm = block_ctr_pxm
class BlockProjectionWorkerSignals(QObject):
didFinish = Signal(BlockProjectionWorker.Result)
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