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

Prototype block comparison feature (compare conventional translatory motion...

Prototype block comparison feature (compare conventional translatory motion compensated block with reprojected motion compensated block)
parent 4c0a0d30
demo-resources/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
......
# 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", 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("Reference (conventional)")
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("Reference (reprojected)")
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")
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,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:
def __init__(self, interpolation, interpolated_block):
self.interpolation = interpolation
self.interpolated_block = interpolated_block
def __init__(self,
image_file,
perspective,
projection,
origin,
blocksize,
center,
control_point_motion_vectors,
motion_model,
interpolation):
super().__init__()
self.image_file = image_file
self.perspective = perspective
self.projection = projection
self.origin = origin
self.blocksize = blocksize
self.center = center
self.control_point_motion_vectors = control_point_motion_vectors
self.motion_model = motion_models[motion_model](blocksize)
self.interpolation = interpolation
self.signals = BlockInterpolationWorkerSignals()
@Slot()
def run(self):
yf, xf = self.getReprojectedBlockPixels()
interpolated_block = self.interpolateBlock(yf, xf)
result = self.Result(self.interpolation, interpolated_block)
self.signals.didFinish.emit(result)
def getReprojectedBlockPixels(self):
yf, xf = np.mgrid[self.origin[1]:self.origin[1]+self.blocksize[1],
self.origin[0]:self.origin[0]+self.blocksize[0]]
xf = xf.reshape(-1) - self.center[0]
yf = yf.reshape(-1) - self.center[1]
# To perspective
rf, phi = coordinate_conversion.cartesian_to_polar(yf, xf)
rp = self.perspective.radius(self.projection.theta(rf))
yp, xp = coordinate_conversion.polar_to_cartesian(rp, phi)
# Block control points
block_center = np.asarray(self.origin) + (np.asarray(self.blocksize) - 1) / 2
r_f, phi = coordinate_conversion.cartesian_to_polar(block_center[1] - self.center[1],
block_center[0] - self.center[0])
r_p = self.projection.transform_to(self.perspective, r_f)
cp0_y, cp0_x = coordinate_conversion.polar_to_cartesian(r_p, phi)
cp0_y = cp0_y - self.blocksize[1] / 2
cp0_x = cp0_x - self.blocksize[0] / 2
# Motion
ypm, xpm = self.motion_model.perform_motion(yp, xp, (cp0_x, cp0_y), self.control_point_motion_vectors)
# To fisheye
rpm, phi = coordinate_conversion.cartesian_to_polar(ypm, xpm)
rfm = self.projection.radius(self.perspective.theta(rpm))
yfm, xfm = coordinate_conversion.polar_to_cartesian(rfm, phi)
yfm = yfm + self.center[1]
xfm = xfm + self.center[0]
return yfm, xfm
def interpolateBlock(self, y, x):
image = imageio.imread(self.image_file)[:, :, :3]
block_image = 255 * np.ones((self.blocksize[1], self.blocksize[0], 3), dtype=image.dtype)
if self.interpolation == 'nearest_neighbor':
y = np.round(y).astype(np.int)
x = np.round(x).astype(np.int)
mask_low = np.logical_and(0 <= y, 0 <= x)
mask_high = np.logical_and(y < image.shape[0], x < image.shape[1])
mask = np.logical_and(mask_low, mask_high)
block_image[mask.reshape((self.blocksize[1], self.blocksize[0]))] = image[y[mask], x[mask]]
elif self.interpolation == 'bicubic':
mask_low = np.logical_and(0 <= y, 0 <= x)
mask_high = np.logical_and(y < image.shape[0], x < image.shape[1])
mask = np.logical_and(mask_low, mask_high)
for channel in range(image.shape[2]):
spline = RectBivariateSpline(range(image.shape[0]), range(image.shape[1]), image[:, :, channel])
channel_image = (255 * np.ones((self.blocksize[1], self.blocksize[0]), dtype=image.dtype))
data = np.clip(spline(y[mask], x[mask], grid=False), 0, 255)
channel_image[mask.reshape((self.blocksize[1], self.blocksize[0]))] = data
block_image[:, :, channel] = channel_image
else:
raise RuntimeError("Invalid interpolation mode.")
return block_image
class BlockInterpolationWorkerSignals(QObject):