Example #1
0
class KeypointAlignmentWidget(QtWidgets.QWidget):
    def __init__(self, parent: 'LayerAlignmentDialog',
                 model: KeypointAlignmentModel) -> None:
        super(KeypointAlignmentWidget, self).__init__()
        self.model = model
        self._parent = parent

        layout = QtWidgets.QFormLayout()
        self.setLayout(layout)

        keypoint_gb = QtWidgets.QGroupBox("Keypoint")
        layout.addWidget(keypoint_gb)

        edit_layout = QtWidgets.QFormLayout()
        keypoint_gb.setLayout(edit_layout)

        self.kpts_sel = QtWidgets.QComboBox()
        self.kpts_sel.setModel(self.model.combo_adapter)
        self.kpts_sel.currentIndexChanged.connect(self.kptChanged)
        edit_layout.addRow("Keypoint:", self.kpts_sel)

        self.wx = UnitLineEdit(UNIT_GROUP_MM)
        self.wy = UnitLineEdit(UNIT_GROUP_MM)
        edit_layout.addRow("World X", self.wx)
        edit_layout.addRow("World Y", self.wy)
        self.wx.edited.connect(self.update_world)
        self.wy.edited.connect(self.update_world)

        self.px = UnitLineEdit(UNIT_GROUP_PX)
        self.py = UnitLineEdit(UNIT_GROUP_PX)
        edit_layout.addRow("Image X", self.px)
        edit_layout.addRow("Image Y", self.py)
        self.px.edited.connect(self.update_layer)
        self.py.edited.connect(self.update_layer)

        self.use_for_alignment = QtWidgets.QCheckBox()
        edit_layout.addRow("Use", self.use_for_alignment)
        self.use_for_alignment.clicked.connect(self.update_used)

        self.add_btn = QtWidgets.QPushButton("Add New")
        self.add_btn.clicked.connect(self.addKeypoint)
        self.del_btn = QtWidgets.QPushButton("Remove Current")
        self.del_btn.clicked.connect(self.delKeypoint)
        bhl = QtWidgets.QHBoxLayout()
        bhl.addWidget(self.add_btn)
        bhl.addWidget(self.del_btn)
        edit_layout.addRow(bhl)

        self.constraint_status_lbl = QtWidgets.QLabel("")
        self.constraint_status_lbl.setWordWrap(True)
        layout.addRow(self.constraint_status_lbl)

        self.model.changed.connect(self.modelChanged)
        self.modelChanged()

    def addKeypoint(self) -> None:
        cmd = cmd_add_keypoint(self.model)
        self._parent.undoStack.push(cmd)
        self.kpts_sel.setCurrentIndex(cmd.index)

    def delKeypoint(self) -> None:
        cmd = cmd_del_keypoint(self.model, self.model.selected_idx)
        self._parent.undoStack.push(cmd)

    def modelChanged(self) -> None:
        cmb_index = -1 if self.model.selected_idx is None else self.model.selected_idx
        self.kpts_sel.blockSignals(True)
        self.kpts_sel.setCurrentIndex(cmb_index)
        self.kpts_sel.blockSignals(False)
        self.updateTextViews()

    def kptChanged(self) -> None:
        """
        Called when keypoint combo-box drop down changed
        :return:
        """
        idx: Optional[int] = self.kpts_sel.currentIndex()
        if idx == -1:
            idx = None
        self.model.selected_idx = idx

    def update_world(self) -> None:
        idx = self.kpts_sel.currentIndex()
        vx = self.wx.getValue()
        vy = self.wy.getValue()
        if vx is None or vy is None:
            # TODO - flash bad box?
            return

        p = Vec2(vx, vy)
        cmd = cmd_set_keypoint_world(self.model, idx, p)
        self._parent.undoStack.push(cmd)

    def update_layer(self) -> None:
        idx = self.kpts_sel.currentIndex()
        vx = self.px.getValue()
        vy = self.py.getValue()
        if vx is None or vy is None:
            # TODO - flash bad box?
            return

        p = Vec2(vx, vy)
        cmd = cmd_set_keypoint_px(self.model, idx, p)
        self._parent.undoStack.push(cmd)

    def update_used(self) -> None:
        idx = self.kpts_sel.currentIndex()
        cmd = cmd_set_keypoint_used(self.model, idx,
                                    self.use_for_alignment.isChecked())
        self._parent.undoStack.push(cmd)

    def updateTextViews(self) -> None:
        idx = self.model.selected_idx

        # Disable keypoint delete when we selected a prior-existing keypoint
        # Also disable delete button when no kp is selected
        self.del_btn.setEnabled(idx is not None
                                and self.model.keypoints[idx].is_new)

        self.updateConstraintLabel()

        # If nothing is selected, clear-out all the edit fields
        if idx is None:
            self.wx.setValue(None)
            self.wy.setValue(None)
            self.px.setValue(None)
            self.py.setValue(None)
            self.wx.setEnabled(False)
            self.wy.setEnabled(False)
            self.px.setEnabled(False)
            self.py.setEnabled(False)
            self.use_for_alignment.setEnabled(False)
            self.use_for_alignment.setChecked(False)
            return

        kpt = self.model.keypoints[idx]

        # Disable editing of old keypoints
        self.wx.setEnabled(kpt.is_new)
        self.wy.setEnabled(kpt.is_new)

        self.wx.setValue(kpt.world.x)
        self.wy.setValue(kpt.world.y)

        self.use_for_alignment.setEnabled(True)
        self.use_for_alignment.setChecked(kpt.use)

        self.px.setEnabled(kpt.use)
        self.py.setEnabled(kpt.use)

        self.px.setValue(kpt.layer.x)
        self.py.setValue(kpt.layer.y)

    def updateConstraintLabel(self) -> None:
        cs = self.model.constraint_info

        if cs == Constraint.Unconstrained:
            self.constraint_status_lbl.setText(
                "Image is unconstrained. Will not be aligned in world space. "
                + "This is probably not what you want")
        elif cs == Constraint.Translation:
            self.constraint_status_lbl.setText(
                "Image is constrained only by one keypoint. This will only position "
                +
                "the image in space, but not provide scale or alignment information. "
                + "This is probably not what you want")
        elif cs == Constraint.Rotate_Scale:
            self.constraint_status_lbl.setText(
                "Image is constrained by two keypoints. This will only position, "
                +
                "proportionally scale and rotate the image. That may be OK for "
                + "scanned images")
        elif cs == Constraint.Orthogonal:
            self.constraint_status_lbl.setText(
                "Image is constrained by three keypoints. This allows alignment of "
                +
                "scale, rotation, translation and shear. This is probably not what "
                +
                "you want, but may be useful for synthetically transformed images"
            )
        elif cs == Constraint.Perspective:
            self.constraint_status_lbl.setText(
                "Image is constrained by four keypoints. This allows for full recovery"
                +
                " of the perspective transform. This is probably what you want "
                + "for camera imagery.")
        elif cs == Constraint.Singular:
            self.constraint_status_lbl.setText(
                "Can't solve for these constraints. Are keypoints overlapping or "
                + "colinear in either world or image space?")
        elif cs == Constraint.Overconstrained:
            self.constraint_status_lbl.setText(
                "Too many constraints to solve for. " +
                "Max 4 enabled keypoints for alignment.")
Example #2
0
class KeypointAlignmentWidget(QtGui.QWidget):
    def __init__(self, parent, model):
        super(KeypointAlignmentWidget, self).__init__()
        self.model = model
        self._parent = parent

        layout = QtGui.QFormLayout()
        self.setLayout(layout)

        keypoint_gb = QtGui.QGroupBox("Keypoint")
        layout.addWidget(keypoint_gb)

        edit_layout = QtGui.QFormLayout()
        keypoint_gb.setLayout(edit_layout)


        self.kpts_sel = QtGui.QComboBox()
        self.kpts_sel.setModel(self.model.combo_adapter)
        self.kpts_sel.currentIndexChanged.connect(self.kptChanged)
        edit_layout.addRow("Keypoint:", self.kpts_sel)

        self.wx = UnitLineEdit(UNIT_GROUP_MM)
        self.wy = UnitLineEdit(UNIT_GROUP_MM)
        edit_layout.addRow("World X", self.wx)
        edit_layout.addRow("World Y", self.wy)
        self.wx.edited.connect(self.update_world)
        self.wy.edited.connect(self.update_world)

        self.px = UnitLineEdit(UNIT_GROUP_PX)
        self.py = UnitLineEdit(UNIT_GROUP_PX)
        edit_layout.addRow("Image X", self.px)
        edit_layout.addRow("Image Y", self.py)
        self.px.edited.connect(self.update_layer)
        self.py.edited.connect(self.update_layer)


        self.use_for_alignment = QtGui.QCheckBox()
        edit_layout.addRow("Use", self.use_for_alignment)
        self.use_for_alignment.clicked.connect(self.update_used)

        self.add_btn = QtGui.QPushButton("Add New")
        self.add_btn.clicked.connect(self.addKeypoint)
        self.del_btn = QtGui.QPushButton("Remove Current")
        self.del_btn.clicked.connect(self.delKeypoint)
        bhl = QtGui.QHBoxLayout()
        bhl.addWidget(self.add_btn)
        bhl.addWidget(self.del_btn)
        edit_layout.addRow(bhl)


        self.constraint_status_lbl = QtGui.QLabel("")
        self.constraint_status_lbl.setWordWrap(True)
        layout.addRow(self.constraint_status_lbl)

        self.model.changed.connect(self.modelChanged)
        self.modelChanged()

    def addKeypoint(self):
        cmd = cmd_add_keypoint(self.model)
        self._parent.undoStack.push(cmd)
        self.kpts_sel.setCurrentIndex(cmd.index)

    def delKeypoint(self):
        cmd = cmd_del_keypoint(self.model, self.model.selected_idx)
        self._parent.undoStack.push(cmd)

    def modelChanged(self):
        cmb_index = -1 if self.model.selected_idx is None else self.model.selected_idx
        self.kpts_sel.blockSignals(True)
        self.kpts_sel.setCurrentIndex(cmb_index)
        self.kpts_sel.blockSignals(False)
        self.updateTextViews()

    def kptChanged(self):
        """
        Called when keypoint combo-box drop down changed
        :return:
        """
        idx = self.kpts_sel.currentIndex()
        if idx == -1:
            idx = None
        self.model.selected_idx = idx

    def update_world(self):
        idx = self.kpts_sel.currentIndex()
        p = Point2(self.wx.getValue(), self.wy.getValue())
        cmd = cmd_set_keypoint_world(self.model, idx, p)
        self._parent.undoStack.push(cmd)

    def update_layer(self):
        idx = self.kpts_sel.currentIndex()
        p = Point2(self.px.getValue(), self.py.getValue())
        cmd = cmd_set_keypoint_px(self.model, idx, p)
        self._parent.undoStack.push(cmd)

    def update_used(self):
        idx = self.kpts_sel.currentIndex()
        cmd = cmd_set_keypoint_used(self.model, idx, self.use_for_alignment.isChecked())
        self._parent.undoStack.push(cmd)

    def updateTextViews(self):
        idx = self.model.selected_idx

        # Disable keypoint delete when we selected a prior-existing keypoint
        # Also disable delete button when no kp is selected
        self.del_btn.setEnabled(idx is not None and self.model.keypoints[idx].is_new)

        self.updateConstraintLabel()

        # If nothing is selected, clear-out all the edit fields
        if idx is None:
            self.wx.setValue(None)
            self.wy.setValue(None)
            self.px.setValue(None)
            self.py.setValue(None)
            self.wx.setEnabled(False)
            self.wy.setEnabled(False)
            self.px.setEnabled(False)
            self.py.setEnabled(False)
            self.use_for_alignment.setEnabled(False)
            self.use_for_alignment.setChecked(False)
            return

        kpt = self.model.keypoints[idx]

        # Disable editing of old keypoints
        self.wx.setEnabled(kpt.is_new)
        self.wy.setEnabled(kpt.is_new)

        self.wx.setValue(kpt.world.x)
        self.wy.setValue(kpt.world.y)

        self.use_for_alignment.setEnabled(True)
        self.use_for_alignment.setChecked(kpt.use)

        self.px.setEnabled(kpt.use)
        self.py.setEnabled(kpt.use)

        self.px.setValue(kpt.layer.x)
        self.py.setValue(kpt.layer.y)

    def updateConstraintLabel(self):
        cs = self.model.constraint_info

        if cs == CONS_UNCONSTRAINED:
            self.constraint_status_lbl.setText("Image is unconstrained. Will not be aligned in world space. " +
                                               "This is probably not what you want")
        elif cs == CONS_TRANSLATION:
            self.constraint_status_lbl.setText("Image is constrained only by one keypoint. This will only position " +
                                               "the image in space, but not provide scale or alignment information. " +
                                               "This is probably not what you want")
        elif cs == CONS_ROTATE_SCALE:
            self.constraint_status_lbl.setText("Image is constrained by two keypoints. This will only position, " +
                                               "proportionally scale and rotate the image. That may be OK for " +
                                               "scanned images")
        elif cs == CONS_ORTHOGONAL:
            self.constraint_status_lbl.setText("Image is constrained by three keypoints. This allows alignment of " +
                                               "scale, rotation, translation and shear. This is probably not what " +
                                               "you want, but may be useful for synthetically transformed images")
        elif cs == CONS_PERSPECTIVE:
            self.constraint_status_lbl.setText("Image is constrained by four keypoints. This allows for full recovery" +
                                               " of the perspective transform. This is probably what you want " +
                                               "for camera imagery.")
        elif cs == CONS_SINGULAR:
            self.constraint_status_lbl.setText("Can't solve for these constraints. Are keypoints overlapping or "+
                                               "colinear in either world or image space?")
        elif cs == CONS_OVERCONSTRAINED:
            self.constraint_status_lbl.setText("Too many constraints to solve for. " +
                                               "Max 4 enabled keypoints for alignment.")