Commit 04384a86 authored by Andy Regensky's avatar Andy Regensky
Browse files

Merge branch 'feature_block_comparison' into 'main'

BETA: Feature block comparison

Implement block comparison functionality comparing motion compensated block if motion is performed in fisheye domain (conventional) vs motion compensated block if motion is performed in perspective domain (reprojection).

See merge request !1
parents 9083f493 076d4101
demo-resources/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
......
......@@ -11,6 +11,7 @@ Download or clone this repository and enter the main folder. Python 3 is require
run the application. Furthermore, the following dependencies need to be
installed:
* numpy
* scipy
* imageio
* pyside2
......@@ -18,7 +19,7 @@ It is recommended to install the dependencies in a dedicated python environment.
the anaconda/miniconda package manager, a new environment named `FishUI` with all
dependencies pre-installed can be created by executing
```sh
$ conda create -n FishUI numpy imageio pyside2
$ conda create -n FishUI numpy scipy imageio pyside2
```
The FishUI application can then be started by activating the environment
and running the fishui module.
......
# Copyright (c) 2020 Chair of Multimedia Communications and Signal Processing (LMS),
# Friedrich-Alexander-University Erlangen-Nürnberg (FAU).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
from PySide2.QtCore import QObject, Slot, QThreadPool, Signal, QTimer
from typing import Optional
import numpy as np
from fishui.MainViewModel import MainViewModel
from fishui.workers import BlockInterpolationWorker
class BlockComparisonViewModel(QObject):
tmcBlockDidChange = Signal(np.ndarray)
ptmcBlockDidChange = Signal(np.ndarray)
def __init__(self, mainViewModel: MainViewModel):
super().__init__(mainViewModel)
self.mainViewModel = mainViewModel
self.mainViewModel.propertiesDidChange.connect(self.updateBlocks)
self.mainViewModel.viewStateDidChange.connect(self.updateBlocks)
self.mainViewModel.imagesDidChange.connect(self.updateBlocks)
self.threadpool = QThreadPool(self)
self.threadpool.setMaxThreadCount(2)
self.tmcDelayTimer = QTimer(self)
self.tmcDelayTimer.setSingleShot(True)
self.tmcDelayTimer.setInterval(100)
self.tmcDelayTimer.timeout.connect(lambda: self.startTMCBlockWorker('bicubic'))
self.ptmcDelayTimer = QTimer(self)
self.ptmcDelayTimer.setSingleShot(True)
self.ptmcDelayTimer.setInterval(100)
self.ptmcDelayTimer.timeout.connect(lambda: self.startPTMCBlockWorker('bicubic'))
self.tmc_block: Optional[np.ndarray] = None
self.ptmc_block: Optional[np.ndarray] = None
def setTMCBlock(self, tmc_block: np.ndarray):
self.tmc_block = tmc_block
self.tmcBlockDidChange.emit(tmc_block)
def setPTMCBlock(self, ptmc_block: np.ndarray):
self.ptmc_block = ptmc_block
self.ptmcBlockDidChange.emit(ptmc_block)
@Slot()
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]),
dtype=np.uint8))
self.setPTMCBlock(255 * np.ones((self.mainViewModel.blocksize[1],
self.mainViewModel.blocksize[0]),
dtype=np.uint8))
return
self.startTMCBlockWorker('nearest_neighbor')
self.startPTMCBlockWorker('nearest_neighbor')
@Slot(str)
def startTMCBlockWorker(self, interpolation):
worker = BlockInterpolationWorker(
self.mainViewModel.currentFisheyeImage,
self.mainViewModel.perspective,
self.mainViewModel.perspective,
self.mainViewModel.origin,
self.mainViewModel.blocksize,
self.mainViewModel.center,
self.mainViewModel.control_point_motion_vectors,
self.mainViewModel.motion_model,
interpolation
)
worker.signals.didFinish.connect(self.tmcBlockInterpolationDidFinish)
self.threadpool.start(worker)
@Slot(str)
def startPTMCBlockWorker(self, interpolation):
worker = BlockInterpolationWorker(
self.mainViewModel.currentFisheyeImage,
self.mainViewModel.perspective,
self.mainViewModel.projection,
self.mainViewModel.origin,
self.mainViewModel.blocksize,
self.mainViewModel.center,
self.mainViewModel.control_point_motion_vectors,
self.mainViewModel.motion_model,
interpolation
)
worker.signals.didFinish.connect(self.ptmcBlockInterpolationDidFinish)
self.threadpool.start(worker)
@Slot(BlockInterpolationWorker.Result)
def tmcBlockInterpolationDidFinish(self, result: BlockInterpolationWorker.Result):
self.setTMCBlock(result.interpolated_block)
if result.interpolation == 'nearest_neighbor':
self.tmcDelayTimer.start()
@Slot(BlockInterpolationWorker.Result)
def ptmcBlockInterpolationDidFinish(self, result: BlockInterpolationWorker.Result):
self.setPTMCBlock(result.interpolated_block)
if result.interpolation == 'nearest_neighbor':
self.ptmcDelayTimer.start()
......@@ -42,6 +42,7 @@ class MainViewModel(QObject):
propertiesDidChange = Signal()
blockProjectionsDidChange = Signal()
imagesDidChange = Signal()
viewStateDidChange = Signal()
def __init__(self, parent=None):
......@@ -89,6 +90,8 @@ class MainViewModel(QObject):
self.block_ctr_py = None
self.block_ctr_pxm = None
self.block_ctr_pym = None
self.block_yfm_tmc = None
self.block_xfm_tmc = None
self.uwc_sign = None
self.updateBlockProjection()
......@@ -97,6 +100,13 @@ class MainViewModel(QObject):
self._perspective_images = {}
self._current_idx = 0
# View State
self.showBlockComparisonWindow = False
def setShowBlockComparisonWindow(self, show=True):
self.showBlockComparisonWindow = show
self.viewStateDidChange.emit()
@property
def center(self):
return (np.asarray(self.sensor_size_px) - 1) / 2
......@@ -223,6 +233,8 @@ class MainViewModel(QObject):
self.block_ctr_px = result.block_ctr_px
self.block_ctr_pym = result.block_ctr_pym
self.block_ctr_pxm = result.block_ctr_pxm
self.block_yfm_tmc = result.block_yfm_tmc
self.block_xfm_tmc = result.block_xfm_tmc
self.blockProjectionsDidChange.emit()
def setFisheyeImages(self, images):
......
......@@ -44,6 +44,8 @@ class MainWindow(QMainWindow):
self.propertiesWidget = widgets.PropertiesWidget(self, self.viewModel)
self.distortionWidget = widgets.DistortionVisualizationWidget(self, self.viewModel)
self.viewModel.viewStateDidChange.connect(self.viewStateDidChange)
# layout
splitter = QSplitter(self)
splitter.addWidget(self.propertiesWidget)
......@@ -57,6 +59,7 @@ class MainWindow(QMainWindow):
mainMenu = self.menuBar()
mainMenu.setNativeMenuBar(False)
fileMenu = mainMenu.addMenu("File")
viewMenu = mainMenu.addMenu("View")
openAction = QAction("Open", self)
openAction.triggered.connect(self.distortionWidget.fisheyeWidget.openImage)
......@@ -70,6 +73,14 @@ class MainWindow(QMainWindow):
savePerspectiveAction.triggered.connect(self.distortionWidget.perspectiveWidget.saveImage)
saveMenu.addAction(savePerspectiveAction)
self.blockComparisonAction = QAction("Block Comparison [BETA]", self)
self.blockComparisonAction.setCheckable(True)
self.blockComparisonAction.triggered.connect(self.blockComparisonActionTriggered)
viewMenu.addAction(self.blockComparisonAction)
self.blockComparisonWindow = widgets.BlockComparisonWindow(self.viewModel)
self.blockComparisonWindow.setVisible(False)
self.setFocus()
self.show()
......@@ -82,4 +93,14 @@ class MainWindow(QMainWindow):
self.viewModel.previousFisheyeImage()
def closeEvent(self, event):
self.blockComparisonWindow.close()
self.viewModel.deleteLater()
@Slot()
def blockComparisonActionTriggered(self):
self.viewModel.setShowBlockComparisonWindow(self.blockComparisonAction.isChecked())
@Slot()
def viewStateDidChange(self):
self.blockComparisonAction.setChecked(self.viewModel.showBlockComparisonWindow)
self.blockComparisonWindow.setVisible(self.viewModel.showBlockComparisonWindow)
# Copyright (c) 2020 Chair of Multimedia Communications and Signal Processing (LMS),
# Friedrich-Alexander-University Erlangen-Nürnberg (FAU).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
from PySide2.QtWidgets import QWidget, QLabel, QHBoxLayout, QVBoxLayout, QSizePolicy
from PySide2.QtCore import Qt, Slot
from PySide2.QtGui import QColor
from fishui.MainViewModel import MainViewModel
from fishui.BlockComparisonViewModel import BlockComparisonViewModel
from fishui.widgets.ImageWidget import ImageWidget
from fishui.widgets.AspectRatioWidget import AspectRatioWidget
class BlockComparisonWindow(QWidget):
def __init__(self, mainViewModel: MainViewModel):
super().__init__(None)
self.viewModel = BlockComparisonViewModel(mainViewModel)
self.viewModel.mainViewModel.propertiesDidChange.connect(self.viewModelChanged)
self.referenceTMCLabel = QLabel(self)
self.referenceTMCLabel.setText("Moved block (motion in fisheye domain)")
self.referenceTMCLabel.setStyleSheet("font-weight: bold; font-size: 12px")
self.referenceTMCLabel.setAlignment(Qt.AlignCenter)
self.referenceTMCLabel.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
self.referenceTMCBlockWidget = ImageWidget(self)
self.referenceTMCBlockWidget.setFrameColor(QColor(44, 160, 44))
self.viewModel.tmcBlockDidChange.connect(self.referenceTMCBlockWidget.updateImage)
self.referenceTMCBlockARWidget = AspectRatioWidget(self.referenceTMCBlockWidget, self)
self.referenceTMCLayout = QVBoxLayout()
self.referenceTMCLayout.addWidget(self.referenceTMCLabel)
self.referenceTMCLayout.addWidget(self.referenceTMCBlockARWidget)
self.referencePTMCLabel = QLabel(self)
self.referencePTMCLabel.setText("Moved block (motion in perspective domain)")
self.referencePTMCLabel.setStyleSheet("font-weight: bold; font-size: 12px")
self.referencePTMCLabel.setAlignment(Qt.AlignCenter)
self.referencePTMCLabel.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
self.referencePTMCBlockWidget = ImageWidget(self)
self.referencePTMCBlockWidget.setFrameColor(QColor(31, 119, 180))
self.viewModel.ptmcBlockDidChange.connect(self.referencePTMCBlockWidget.updateImage)
self.referencePTMCBlockARWidget = AspectRatioWidget(self.referencePTMCBlockWidget, self)
self.referencePTMCLayout = QVBoxLayout()
self.referencePTMCLayout.addWidget(self.referencePTMCLabel)
self.referencePTMCLayout.addWidget(self.referencePTMCBlockARWidget)
layout = QHBoxLayout()
layout.addLayout(self.referenceTMCLayout)
layout.addLayout(self.referencePTMCLayout)
self.setLayout(layout)
self.setWindowTitle("Block Comparison [BETA]")
self.show()
def closeEvent(self, event):
self.viewModel.mainViewModel.setShowBlockComparisonWindow(False)
@Slot()
def viewModelChanged(self):
aspect_ratio = self.viewModel.mainViewModel.blocksize[0] / self.viewModel.mainViewModel.blocksize[1]
self.referenceTMCBlockARWidget.setAspectRatio(aspect_ratio)
self.referencePTMCBlockARWidget.setAspectRatio(aspect_ratio)
......@@ -43,8 +43,10 @@ class FisheyeBlockWidget(QWidget):
self.viewModel = viewModel
self.viewModel.propertiesDidChange.connect(self.updatePolygons)
self.viewModel.blockProjectionsDidChange.connect(self.updatePolygons)
self.viewModel.viewStateDidChange.connect(self.update)
self.fisheyePolygon = None
self.fisheyeTMCPolygon = None
self.reprojectPolygon = None
self.updatePolygons(update=False)
......@@ -56,13 +58,16 @@ class FisheyeBlockWidget(QWidget):
@Slot()
def updatePolygons(self, update=True):
fisheyePolygon = QPolygonF()
fisheyeTMCPolygon = QPolygonF()
reprojectPolygon = QPolygonF()
if self.viewModel.block_xf is None:
return
for idx in range(len(self.viewModel.block_xf)):
fisheyePolygon.append(QPointF(self.viewModel.block_xf[idx], self.viewModel.block_yf[idx]))
fisheyeTMCPolygon.append(QPointF(self.viewModel.block_xfm_tmc[idx], self.viewModel.block_yfm_tmc[idx]))
reprojectPolygon.append(QPointF(self.viewModel.block_xfm[idx], self.viewModel.block_yfm[idx]))
self.fisheyePolygon = fisheyePolygon
self.fisheyeTMCPolygon = fisheyeTMCPolygon
self.reprojectPolygon = reprojectPolygon
if update:
self.update()
......@@ -84,6 +89,14 @@ class FisheyeBlockWidget(QWidget):
def drawPolygons(self, event, painter):
orange = QColor(255, 127, 14)
blue = QColor(31, 119, 180)
green = QColor(44, 160, 44)
if self.viewModel.showBlockComparisonWindow:
pen = QPen(green)
pen.setWidth(painter.window().width() / 200)
painter.setPen(pen)
if self.fisheyeTMCPolygon:
painter.drawPolygon(self.fisheyeTMCPolygon)
pen = QPen(blue)
pen.setWidth(painter.window().width()/200)
......
# Copyright (c) 2020 Chair of Multimedia Communications and Signal Processing (LMS),
# Friedrich-Alexander-University Erlangen-Nürnberg (FAU).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
import numpy as np
from PySide2.QtGui import QPainter, QImage, QColor, QPen
from PySide2.QtWidgets import QWidget
from PySide2.QtCore import QSize, QRect, QRectF, Slot, Qt
class ImageWidget(QWidget):
def __init__(self, parent, image=255 * np.ones((64, 64), dtype=np.uint8)):
super().__init__(parent)
self.image = image
self.color = Qt.white
self.show()
@Slot(np.ndarray)
def updateImage(self, image):
self.image = image
self.update()
def setFrameColor(self, color: QColor):
self.color = color
self.update()
def sizeHint(self):
return QSize(256, 256)
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
painter.setWindow(QRect(0,
0,
self.image.shape[1],
self.image.shape[0]))
painter.fillRect(painter.window(), QColor(255, 255, 255))
self.drawImage(painter)
self.drawFrame(painter)
painter.end()
def drawImage(self, painter):
if self.image is None:
return
image = self.image
image = QImage(image.data,
image.shape[1],
image.shape[0],
image.strides[0],
QImage.Format_RGB888)
rect = QRectF(0,
0,
self.image.shape[1],
self.image.shape[0])
painter.drawImage(rect, image, rect)
def drawFrame(self, painter):
pen = QPen(self.color)
pen.setWidth(painter.window().width() / 25)
painter.setPen(pen)
painter.drawRect(painter.window())
......@@ -30,7 +30,7 @@
import sys
from PySide2.QtGui import QPainter, QColor, QPen, QPalette, QPolygonF
from PySide2.QtGui import QPainter, QColor, QPen, QPalette, QPolygonF, QBrush
from PySide2.QtWidgets import QWidget, QApplication
from PySide2.QtCore import QSize, Qt, QPointF, QRect, Slot
from fishui.MainViewModel import MainViewModel
......@@ -50,6 +50,8 @@ class PerspectiveBlockWidget(QWidget):
self.perspectiveMovedPolygon = None
self.controlPointPolygon = None
self.controlPointMovedPolygon = None
self.controlPointPoints = None
self.controlPointMovedPoints = None
self.updatePolygon(update=False)
self.initialCPMVs = None
......@@ -77,7 +79,7 @@ class PerspectiveBlockWidget(QWidget):
perspectiveMovedPolygon.append(QPointF(block_xpm[idx], block_ypm[idx]))
self.perspectivePolygon = perspectivePolygon
self.perspectiveMovedPolygon = perspectiveMovedPolygon
if self.viewModel.motion_model != 'translatory':
if self.viewModel.motion_model == 'affine4' or self.viewModel.motion_model == 'affine6':
if self.viewModel.block_ctr_px is None:
return
ctr_px = self.viewModel.block_ctr_px + sensor_offset_x
......@@ -89,6 +91,18 @@ class PerspectiveBlockWidget(QWidget):
controlPointMovedPolygon.append(QPointF(ctr_pxm[idx], ctr_pym[idx]))
self.controlPointPolygon = controlPointPolygon
self.controlPointMovedPolygon = controlPointMovedPolygon
if self.viewModel.motion_model == "affine4":
self.controlPointPoints = [controlPointPolygon.at(0),
controlPointPolygon.at(1)]
self.controlPointMovedPoints = [controlPointMovedPolygon.at(0),
controlPointMovedPolygon.at(1)]
else:
self.controlPointPoints = [controlPointPolygon.at(0),
controlPointPolygon.at(1),
controlPointPolygon.at(3)]
self.controlPointMovedPoints = [controlPointMovedPolygon.at(0),
controlPointMovedPolygon.at(1),
controlPointMovedPolygon.at(3)]
if update:
self.update()
......@@ -101,9 +115,9 @@ class PerspectiveBlockWidget(QWidget):
painter.setWindow(QRect(0, 0, self.viewModel.perspective_scale * self.viewModel.sensor_size_px[0],
self.viewModel.perspective_scale * self.viewModel.sensor_size_px[1]))
painter.setRenderHint(QPainter.Antialiasing)
self.drawPolygon(event, painter)
if self.viewModel.motion_model != 'translatory':
if self.viewModel.motion_model == 'affine4' or self.viewModel.motion_model == 'affine6':
self.drawControlPointBlock(event, painter)
self.drawPolygon(event, painter)
painter.end()
def drawPolygon(self, event, painter):
......@@ -127,11 +141,21 @@ class PerspectiveBlockWidget(QWidget):
pen = QPen()
pen.setColor(color)
pen.setWidth(painter.window().width()/400)
painter.setPen(pen)
brush = painter.brush()
if self.controlPointPolygon:
painter.setPen(pen)
painter.drawPolygon(self.controlPointPolygon)
painter.setBrush(color)
for point in self.controlPointPoints:
painter.drawEllipse(point, 2 * pen.width(), 2 * pen.width())
painter.setBrush(brush)
if self.controlPointMovedPolygon:
painter.setPen(pen)
painter.drawPolygon(self.controlPointMovedPolygon)
painter.setBrush(color)
for point in self.controlPointMovedPoints:
painter.drawEllipse(point, 2 * pen.width(), 2 * pen.width())
painter.setBrush(brush)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
......
......@@ -30,9 +30,11 @@
from .AspectRatioWidget import AspectRatioWidget
from .BlockComparisonWindow import BlockComparisonWindow
from .FisheyeBackgroundWidget import FisheyeBackgroundWidget
from .FisheyeBlockWidget import FisheyeBlockWidget
from .FisheyeWidget import FisheyeWidget
from .ImageWidget import ImageWidget
from .PerspectiveBackgroundWidget import PerspectiveBackgroundWidget
from .PerspectiveBlockWidget import PerspectiveBlockWidget
from .PerspectiveWidget import PerspectiveWidget
......
# Copyright (c) 2020 Chair of Multimedia Communications and Signal Processing (LMS),
# Friedrich-Alexander-University Erlangen-Nürnberg (FAU).
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
from PySide2.QtCore import QObject, QRunnable, Signal, Slot
from fishui import coordinate_conversion
from fishui.model import motion_models
import numpy as np
from scipy.interpolate import RectBivariateSpline
import imageio
class BlockInterpolationWorker(QRunnable):
class Result: