# Path: pyteltools/geom/transformation.py
# class Transformation:
#     """!
#     @brief Transformation between two coordinate systems
#     A transformation is a composition of rotation, scaling and translation on 3d vectors.
#     """
#     def __init__(self, angle, horizontal_factor, vertical_factor, dx, dy, dz):
#         self.rotation = Rotation(angle)
#         self.scaling = Scaling(horizontal_factor, vertical_factor)
#         self.translation = Translation(dx, dy, dz)
# 
#     def __repr__(self):
#         return ' '.join(map(str, [self.rotation.angle, self.scaling.horizontal_factor, self.scaling.vertical_factor,
#                                   self.translation.dx, self.translation.dy, self.translation.dz]))
# 
#     def __call__(self, coordinates):
#         return self.translation.vector + self.scaling.vector * self.rotation.rotation_matrix.dot(coordinates)
# 
#     def __str__(self):
#         return '\n'.join(['Rotation:\t%.4f (rad)' % self.rotation.angle,
#                           'Scaling:\tXY %.4f \tZ %.4f ' %
#                           (self.scaling.horizontal_factor, self.scaling.vertical_factor),
#                           'Translation:\tX %.4f \t Y %.4f \t Z %.4f' %
#                           (self.translation.dx, self.translation.dy, self.translation.dz)])
# 
#     def inverse(self):
#         """!
#         @brief The inverse transformation
#         The inverse transformation is a transformation with inverse rotation, inverse scaling
#         and a translation vector that is equal to the result of inverse rotation and inverse scaling
#         of the inverse translation vector
#         """
#         inverse_rotation = self.rotation.inverse()
#         inverse_scaling = self.scaling.inverse()
#         inverse_translation = self.translation.inverse()
#         inverse_dx, inverse_dy, inverse_dz = inverse_scaling.vector * \
#                                              inverse_rotation.rotation_matrix.dot(inverse_translation.vector)
#         return Transformation(inverse_rotation.angle,
#                               inverse_scaling.horizontal_factor, inverse_scaling.vertical_factor,
#                               inverse_dx, inverse_dy, inverse_dz)
# 
# def is_connected(nodes, edge_list):
#     """!
#     @brief ad hoc function for checking if an undirected graph is connected
#     @param nodes <list>: the list of nodes in the graph
#     @param edge_list <iterable>: edges in the graph
#     @return <bool>: True if the graph is connected
#     """
#     adj = {}
#     for i in nodes:
#         adj[i] = set()
#     for u, v in edge_list:
#         adj[u].add(v)
# 
#     visited = {}
#     for node in nodes:
#         visited[node] = False
# 
#     stack = [nodes[0]]
#     while stack:
#         current_node = stack.pop()
#         if not visited[current_node]:
#             visited[current_node] = True
#             for neighbor in adj[current_node]:
#                 stack.append(neighbor)
#     return all(visited.values())
# 
# def transformation_optimization(from_points, to_points, ignore_z):
#     """!
#     @brief Wrapper for optimization methods for transformations from one coordinate system to another
#     @param from_points <list>: coordinates of points in the first coordinate system
#     @param to_points <list>: the coordinates of the same points in the second coordinate system
#     @param ignore_z <bool>: if True, optimize for 4 parameters instead of 6 (identity along z-axis)
#     @return <tuple>: the final transformation, final cost function value, boolean indicating success and a message
#     """
#     if ignore_z:
#         return four_parameters_optimization(from_points, to_points)
#     return six_parameters_optimization(from_points, to_points)


import numpy as np
import sys
from PyQt5.QtCore import (QPoint, QRect, Qt)
from PyQt5.QtGui import (QBrush, QColor, QPainter, QPen)
from PyQt5.QtWidgets import (QApplication, QDialog, QDialogButtonBox, QFileDialog, QGridLayout, QGroupBox,
                             QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPlainTextEdit, QPushButton,
                             QRadioButton, QSpacerItem, QStatusBar, QVBoxLayout, QWidget)
from pyteltools.geom.transformation import Transformation, is_connected, transformation_optimization as optimize
                else:
                    coord = tuple(map(float, line.split(separator)))

                if format_xy:
                    to_points.append(np.array([coord[0], coord[1], 0]))
                else:
                    to_points.append(np.array([coord[0], coord[1], coord[2]]))
        except (ValueError, IndexError):
            QMessageBox.critical(self, 'Error', 'Invalid coordinates in %s.' % self.to_label,
                                 QMessageBox.Ok)
            return False, [], []
        if not to_points:
            QMessageBox.critical(self, 'Error', 'Empty input in %s.' % self.to_label,
                                 QMessageBox.Ok)
            return False, [], []
        if len(from_points) != len(to_points):
            QMessageBox.critical(self, 'Error', 'Two systems should have the same number of coordinates!',
                                 QMessageBox.Ok)
            return False, [], []
        if len(from_points) < 3:
            QMessageBox.critical(self, 'Error', 'Please enter the coordinates of at least 3 points.',
                                 QMessageBox.Ok)
            return False, [], []
        return True, from_points, to_points

    def run(self):
        ready, from_points, to_points = self._getPoints()
        if not ready:
            return
        ignore_z = self.xyButton.isChecked()

# Path: pyteltools/geom/transformation.py
# class Transformation:
#     """!
#     @brief Transformation between two coordinate systems
#     A transformation is a composition of rotation, scaling and translation on 3d vectors.
#     """
#     def __init__(self, angle, horizontal_factor, vertical_factor, dx, dy, dz):
#         self.rotation = Rotation(angle)
#         self.scaling = Scaling(horizontal_factor, vertical_factor)
#         self.translation = Translation(dx, dy, dz)
# 
#     def __repr__(self):
#         return ' '.join(map(str, [self.rotation.angle, self.scaling.horizontal_factor, self.scaling.vertical_factor,
#                                   self.translation.dx, self.translation.dy, self.translation.dz]))
# 
#     def __call__(self, coordinates):
#         return self.translation.vector + self.scaling.vector * self.rotation.rotation_matrix.dot(coordinates)
# 
#     def __str__(self):
#         return '\n'.join(['Rotation:\t%.4f (rad)' % self.rotation.angle,
#                           'Scaling:\tXY %.4f \tZ %.4f ' %
#                           (self.scaling.horizontal_factor, self.scaling.vertical_factor),
#                           'Translation:\tX %.4f \t Y %.4f \t Z %.4f' %
#                           (self.translation.dx, self.translation.dy, self.translation.dz)])
# 
#     def inverse(self):
#         """!
#         @brief The inverse transformation
#         The inverse transformation is a transformation with inverse rotation, inverse scaling
#         and a translation vector that is equal to the result of inverse rotation and inverse scaling
#         of the inverse translation vector
#         """
#         inverse_rotation = self.rotation.inverse()
#         inverse_scaling = self.scaling.inverse()
#         inverse_translation = self.translation.inverse()
#         inverse_dx, inverse_dy, inverse_dz = inverse_scaling.vector * \
#                                              inverse_rotation.rotation_matrix.dot(inverse_translation.vector)
#         return Transformation(inverse_rotation.angle,
#                               inverse_scaling.horizontal_factor, inverse_scaling.vertical_factor,
#                               inverse_dx, inverse_dy, inverse_dz)
# 
# def is_connected(nodes, edge_list):
#     """!
#     @brief ad hoc function for checking if an undirected graph is connected
#     @param nodes <list>: the list of nodes in the graph
#     @param edge_list <iterable>: edges in the graph
#     @return <bool>: True if the graph is connected
#     """
#     adj = {}
#     for i in nodes:
#         adj[i] = set()
#     for u, v in edge_list:
#         adj[u].add(v)
# 
#     visited = {}
#     for node in nodes:
#         visited[node] = False
# 
#     stack = [nodes[0]]
#     while stack:
#         current_node = stack.pop()
#         if not visited[current_node]:
#             visited[current_node] = True
#             for neighbor in adj[current_node]:
#                 stack.append(neighbor)
#     return all(visited.values())
# 
# def transformation_optimization(from_points, to_points, ignore_z):
#     """!
#     @brief Wrapper for optimization methods for transformations from one coordinate system to another
#     @param from_points <list>: coordinates of points in the first coordinate system
#     @param to_points <list>: the coordinates of the same points in the second coordinate system
#     @param ignore_z <bool>: if True, optimize for 4 parameters instead of 6 (identity along z-axis)
#     @return <tuple>: the final transformation, final cost function value, boolean indicating success and a message
#     """
#     if ignore_z:
#         return four_parameters_optimization(from_points, to_points)
#     return six_parameters_optimization(from_points, to_points)


# Path: pyteltools/gui/ConfigTransformation.py
import numpy as np
import sys
from PyQt5.QtCore import (QPoint, QRect, Qt)
from PyQt5.QtGui import (QBrush, QColor, QPainter, QPen)
from PyQt5.QtWidgets import (QApplication, QDialog, QDialogButtonBox, QFileDialog, QGridLayout, QGroupBox,
                             QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPlainTextEdit, QPushButton,
                             QRadioButton, QSpacerItem, QStatusBar, QVBoxLayout, QWidget)
from pyteltools.geom.transformation import Transformation, is_connected, transformation_optimization as optimize

                else:
                    coord = tuple(map(float, line.split(separator)))

                if format_xy:
                    to_points.append(np.array([coord[0], coord[1], 0]))
                else:
                    to_points.append(np.array([coord[0], coord[1], coord[2]]))
        except (ValueError, IndexError):
            QMessageBox.critical(self, 'Error', 'Invalid coordinates in %s.' % self.to_label,
                                 QMessageBox.Ok)
            return False, [], []
        if not to_points:
            QMessageBox.critical(self, 'Error', 'Empty input in %s.' % self.to_label,
                                 QMessageBox.Ok)
            return False, [], []
        if len(from_points) != len(to_points):
            QMessageBox.critical(self, 'Error', 'Two systems should have the same number of coordinates!',
                                 QMessageBox.Ok)
            return False, [], []
        if len(from_points) < 3:
            QMessageBox.critical(self, 'Error', 'Please enter the coordinates of at least 3 points.',
                                 QMessageBox.Ok)
            return False, [], []
        return True, from_points, to_points

    def run(self):
        ready, from_points, to_points = self._getPoints()
        if not ready:
            return
        ignore_z = self.xyButton.isChecked()

        self.trans, error, self.success, message = optimize(from_points, to_points, ignore_z)