Example #1
0
class AddressWidget(QTabWidget):
    """ The central widget of the application. Most of the addressbook's
        functionality is contained in this class.
    """

    selectionChanged = Signal(QItemSelection)

    def __init__(self, parent=None):
        """ Initialize the AddressWidget. """
        super(AddressWidget, self).__init__(parent)

        self.tableModel = TableModel()
        self.newAddressTab = NewAddressTab()
        self.newAddressTab.sendDetails.connect(self.addEntry)

        self.addTab(self.newAddressTab, "Address Book")

        self.setupTabs()

    def addEntry(self, name=None, address=None):
        """ Add an entry to the addressbook. """
        if name is None and address is None:
            addDialog = AddDialogWidget()

            if addDialog.exec_():
                name = addDialog.name
                address = addDialog.address

        address = {"name": name, "address": address}
        addresses = self.tableModel.addresses[:]

        # The QT docs for this example state that what we're doing here
        # is checking if the entered name already exists. What they
        # (and we here) are actually doing is checking if the whole
        # name/address pair exists already - ok for the purposes of this
        # example, but obviously not how a real addressbook application
        # should behave.
        try:
            addresses.remove(address)
            QMessageBox.information(self, "Duplicate Name",
                                    "The name \"%s\" already exists." % name)
        except ValueError:
            # The address didn't already exist, so let's add it to the model.

            # Step 1: create the  row
            self.tableModel.insertRows(0)

            # Step 2: get the index of the newly created row and use it.
            # to set the name
            ix = self.tableModel.index(0, 0, QModelIndex())
            self.tableModel.setData(ix, address["name"], Qt.EditRole)

            # Step 3: lather, rinse, repeat for the address.
            ix = self.tableModel.index(0, 1, QModelIndex())
            self.tableModel.setData(ix, address["address"], Qt.EditRole)

            # Remove the newAddressTab, as we now have at least one
            # address in the model.
            self.removeTab(self.indexOf(self.newAddressTab))

            # The screenshot for the QT example shows nicely formatted
            # multiline cells, but the actual application doesn't behave
            # quite so nicely, at least on Ubuntu. Here we resize the newly
            # created row so that multiline addresses look reasonable.
            tableView = self.currentWidget()
            tableView.resizeRowToContents(ix.row())

    def editEntry(self):
        """ Edit an entry in the addressbook. """
        tableView = self.currentWidget()
        proxyModel = tableView.model()
        selectionModel = tableView.selectionModel()

        # Get the name and address of the currently selected row.
        indexes = selectionModel.selectedRows()

        for index in indexes:
            row = proxyModel.mapToSource(index).row()
            ix = self.tableModel.index(row, 0, QModelIndex())
            name = self.tableModel.data(ix, Qt.DisplayRole)
            ix = self.tableModel.index(row, 1, QModelIndex())
            address = self.tableModel.data(ix, Qt.DisplayRole)

        # Open an addDialogWidget, and only allow the user to edit the address.
        addDialog = AddDialogWidget()
        addDialog.setWindowTitle("Edit a Contact")

        addDialog.nameText.setReadOnly(True)
        addDialog.nameText.setText(name)
        addDialog.addressText.setText(address)

        # If the address is different, add it to the model.
        if addDialog.exec_():
            newAddress = addDialog.address
            if newAddress != address:
                ix = self.tableModel.index(row, 1, QModelIndex())
                self.tableModel.setData(ix, newAddress, Qt.EditRole)

    def removeEntry(self):
        """ Remove an entry from the addressbook. """
        tableView = self.currentWidget()
        proxyModel = tableView.model()
        selectionModel = tableView.selectionModel()

        # Just like editEntry, but this time remove the selected row.
        indexes = selectionModel.selectedRows()

        for index in indexes:
            row = proxyModel.mapToSource(index).row()
            self.tableModel.removeRows(row)

        # If we've removed the last address in the model, display the
        # newAddressTab
        if self.tableModel.rowCount() == 0:
            self.insertTab(0, self.newAddressTab, "Address Book")

    def setupTabs(self):
        """ Setup the various tabs in the AddressWidget. """
        groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ"]

        for group in groups:
            proxyModel = QSortFilterProxyModel(self)
            proxyModel.setSourceModel(self.tableModel)
            proxyModel.setDynamicSortFilter(True)

            tableView = QTableView()
            tableView.setModel(proxyModel)
            tableView.setSortingEnabled(True)
            tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
            tableView.horizontalHeader().setStretchLastSection(True)
            tableView.verticalHeader().hide()
            tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
            tableView.setSelectionMode(QAbstractItemView.SingleSelection)

            # This here be the magic: we use the group name (e.g. "ABC") to
            # build the regex for the QSortFilterProxyModel for the group's
            # tab. The regex will end up looking like "^[ABC].*", only
            # allowing this tab to display items where the name starts with
            # "A", "B", or "C". Notice that we set it to be case-insensitive.
            reFilter = "^[%s].*" % group

            proxyModel.setFilterRegExp(QRegExp(reFilter, Qt.CaseInsensitive))
            proxyModel.setFilterKeyColumn(0) # Filter on the "name" column
            proxyModel.sort(0, Qt.AscendingOrder)

            # This prevents an application crash (see: http://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash)
            viewselectionmodel = tableView.selectionModel()
            tableView.selectionModel().selectionChanged.connect(self.selectionChanged)

            self.addTab(tableView, group)

    # Note: the QT example uses a QDataStream for the saving and loading.
    # Here we're using a python dictionary to store the addresses, which
    # can't be streamed using QDataStream, so we just use cpickle for this
    # example.
    def readFromFile(self, filename):
        """ Read contacts in from a file. """
        try:
            f = open(filename, "rb")
            addresses = pickle.load(f)
        except IOError:
            QMessageBox.information(self, "Unable to open file: %s" % filename)
        finally:
            f.close()

        if len(addresses) == 0:
            QMessageBox.information(self, "No contacts in file: %s" % filename)
        else:
            for address in addresses:
                self.addEntry(address["name"], address["address"])

    def writeToFile(self, filename):
        """ Save all contacts in the model to a file. """
        try:
            f = open(filename, "wb")
            pickle.dump(self.tableModel.addresses, f)

        except IOError:
            QMessageBox.information(self, "Unable to open file: %s" % filename)
        finally:
            f.close()
Example #2
0
class AddressWidget(QTabWidget):
    """ The central widget of the application. Most of the addressbook's
        functionality is contained in this class.
    """

    selectionChanged = Signal(QItemSelection)

    def __init__(self, parent=None):
        """ Initialize the AddressWidget. """
        super(AddressWidget, self).__init__(parent)

        self.tableModel = TableModel()
        self.newAddressTab = NewAddressTab()
        self.newAddressTab.sendDetails.connect(self.addEntry)

        self.addTab(self.newAddressTab, "Address Book")

        self.setupTabs()

    def addEntry(self, name=None, address=None):
        """ Add an entry to the addressbook. """
        if name is None and address is None:
            addDialog = AddDialogWidget()

            if addDialog.exec_():
                name = addDialog.name
                address = addDialog.address

        address = {"name": name, "address": address}
        addresses = self.tableModel.addresses[:]

        # The QT docs for this example state that what we're doing here
        # is checking if the entered name already exists. What they
        # (and we here) are actually doing is checking if the whole
        # name/address pair exists already - ok for the purposes of this
        # example, but obviously not how a real addressbook application
        # should behave.
        try:
            addresses.remove(address)
            QMessageBox.information(self, "Duplicate Name",
                                    "The name \"%s\" already exists." % name)
        except ValueError:
            # The address didn't already exist, so let's add it to the model.

            # Step 1: create the  row
            self.tableModel.insertRows(0)

            # Step 2: get the index of the newly created row and use it.
            # to set the name
            ix = self.tableModel.index(0, 0, QModelIndex())
            self.tableModel.setData(ix, address["name"], Qt.EditRole)

            # Step 3: lather, rinse, repeat for the address.
            ix = self.tableModel.index(0, 1, QModelIndex())
            self.tableModel.setData(ix, address["address"], Qt.EditRole)

            # Remove the newAddressTab, as we now have at least one
            # address in the model.
            self.removeTab(self.indexOf(self.newAddressTab))

            # The screenshot for the QT example shows nicely formatted
            # multiline cells, but the actual application doesn't behave
            # quite so nicely, at least on Ubuntu. Here we resize the newly
            # created row so that multiline addresses look reasonable.
            tableView = self.currentWidget()
            tableView.resizeRowToContents(ix.row())

    def editEntry(self):
        """ Edit an entry in the addressbook. """
        tableView = self.currentWidget()
        proxyModel = tableView.model()
        selectionModel = tableView.selectionModel()

        # Get the name and address of the currently selected row.
        indexes = selectionModel.selectedRows()

        for index in indexes:
            row = proxyModel.mapToSource(index).row()
            ix = self.tableModel.index(row, 0, QModelIndex())
            name = self.tableModel.data(ix, Qt.DisplayRole)
            ix = self.tableModel.index(row, 1, QModelIndex())
            address = self.tableModel.data(ix, Qt.DisplayRole)

        # Open an addDialogWidget, and only allow the user to edit the address.
        addDialog = AddDialogWidget()
        addDialog.setWindowTitle("Edit a Contact")

        addDialog.nameText.setReadOnly(True)
        addDialog.nameText.setText(name)
        addDialog.addressText.setText(address)

        # If the address is different, add it to the model.
        if addDialog.exec_():
            newAddress = addDialog.address
            if newAddress != address:
                ix = self.tableModel.index(row, 1, QModelIndex())
                self.tableModel.setData(ix, newAddress, Qt.EditRole)

    def removeEntry(self):
        """ Remove an entry from the addressbook. """
        tableView = self.currentWidget()
        proxyModel = tableView.model()
        selectionModel = tableView.selectionModel()

        # Just like editEntry, but this time remove the selected row.
        indexes = selectionModel.selectedRows()

        for index in indexes:
            row = proxyModel.mapToSource(index).row()
            self.tableModel.removeRows(row)

        # If we've removed the last address in the model, display the
        # newAddressTab
        if self.tableModel.rowCount() == 0:
            self.insertTab(0, self.newAddressTab, "Address Book")

    def setupTabs(self):
        """ Setup the various tabs in the AddressWidget. """
        groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ"]

        for group in groups:
            proxyModel = QSortFilterProxyModel(self)
            proxyModel.setSourceModel(self.tableModel)
            proxyModel.setDynamicSortFilter(True)

            tableView = QTableView()
            tableView.setModel(proxyModel)
            tableView.setSortingEnabled(True)
            tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
            tableView.horizontalHeader().setStretchLastSection(True)
            tableView.verticalHeader().hide()
            tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
            tableView.setSelectionMode(QAbstractItemView.SingleSelection)

            # This here be the magic: we use the group name (e.g. "ABC") to
            # build the regex for the QSortFilterProxyModel for the group's
            # tab. The regex will end up looking like "^[ABC].*", only
            # allowing this tab to display items where the name starts with
            # "A", "B", or "C". Notice that we set it to be case-insensitive.
            reFilter = "^[%s].*" % group

            proxyModel.setFilterRegExp(QRegExp(reFilter, Qt.CaseInsensitive))
            proxyModel.setFilterKeyColumn(0)  # Filter on the "name" column
            proxyModel.sort(0, Qt.AscendingOrder)

            #tableView.selectionModel().selectionChanged.connect(self.selectionChanged)

            self.addTab(tableView, group)

    # Note: the QT example uses a QDataStream for the saving and loading.
    # Here we're using a python dictionary to store the addresses, which
    # can't be streamed using QDataStream, so we just use cpickle for this
    # example.
    def readFromFile(self, filename):
        """ Read contacts in from a file. """
        try:
            f = open(filename, "rb")
            addresses = pickle.load(f)
        except IOError:
            QMessageBox.information(self, "Unable to open file: %s" % filename)
        finally:
            f.close()

        if len(addresses) == 0:
            QMessageBox.information(self, "No contacts in file: %s" % filename)
        else:
            for address in addresses:
                self.addEntry(address["name"], address["address"])

    def writeToFile(self, filename):
        """ Save all contacts in the model to a file. """
        try:
            f = open(filename, "wb")
            pickle.dump(self.tableModel.addresses, f)

        except IOError:
            QMessageBox.information(self, "Unable to open file: %s" % filename)
        finally:
            f.close()
Example #3
0
class ImageSimilarity(QWidget):
    """
    Main class
    """
    def __init__(self, parent=None):
        super(ImageSimilarity, self).__init__(parent)

        #Build window
        self.setWindowTitle('Image Similarity Search')
        self.radio_pca = QRadioButton('PCA')
        self.radio_pca.setChecked(True)
        self.radio_deep = QRadioButton('Convolutional')
        self.button_load_weights = QPushButton('Load Weights')
        self.button_build_DB = QPushButton('Build Database')
        self.button_save_DB = QPushButton('Save Database')
        self.button_load_DB = QPushButton('Load Database')
        self.button_load_img = QPushButton('Load Image')
        self.button_search = QPushButton('Search')
        self.button_search.setEnabled(False)
        self.button_save_result = QPushButton('Save Result')
        self.button_save_result.setEnabled(False)
        self.spinbox = QSpinBox()
        self.spinbox.setMinimum(-1)
        self.spinbox.setMaximum(99999)
        self.spinbox.setValue(0)
        self.spinboxlabel = QLabel('Max. Distance:')
        self.spinboxlabel.setAlignment(Qt.AlignRight)
        self.searchlabel = QLabel('Query Image:')
        self.countlabel = QLabel('# of Hits:')
        self.table = QTableView()
        #self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.table.horizontalHeader().setStretchLastSection(True)
        self.model = TableModel(parent=self)
        self.table.setModel(self.model)
        self.table.setSortingEnabled(True)
        self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)

        hbox_radio = QHBoxLayout()
        hbox_radio.addWidget(self.radio_pca)
        hbox_radio.addWidget(self.radio_deep)
        hbox_radio.addWidget(self.button_load_weights)

        hbox_buttons = QHBoxLayout()
        hbox_buttons.addWidget(self.button_build_DB)
        hbox_buttons.addWidget(self.button_load_DB)
        hbox_buttons.addWidget(self.button_save_DB)
        hbox_buttons.addWidget(self.button_load_img)
        hbox_buttons.addWidget(self.button_search)
        hbox_buttons.addWidget(self.spinboxlabel)
        hbox_buttons.addWidget(self.spinbox)

        radio_widget = QWidget()
        radio_widget.setLayout(hbox_radio)

        button_widget = QWidget()
        button_widget.setLayout(hbox_buttons)

        external_layout = QVBoxLayout()
        external_layout.addWidget(radio_widget)
        external_layout.addWidget(button_widget)
        external_layout.addWidget(self.searchlabel)
        external_layout.addWidget(self.countlabel)
        external_layout.addWidget(self.table)
        external_layout.addWidget(self.button_save_result)

        self.setLayout(external_layout)

        #Connect signals and slots
        self.radio_pca.toggled.connect(lambda: self.switch_NN(self.radio_pca))
        self.radio_deep.toggled.connect(
            lambda: self.switch_NN(self.radio_deep))
        self.button_load_weights.clicked.connect(self.load_weights)
        self.button_build_DB.clicked.connect(self.build_DB)
        self.button_load_DB.clicked.connect(self.load_DB)
        self.button_save_DB.clicked.connect(self.save_DB)
        self.button_load_img.clicked.connect(self.load_image)
        self.button_search.clicked.connect(self.search)
        self.button_save_result.clicked.connect(self.save_search)

        #Build neural network
        self.encoder = build_pca_autoencoder()

        #See if a database already exists
        if not os.path.isfile(os.path.join(application_path, 'std_db_pca.db')):
            self.firstTimeStart(os.path.join(application_path,
                                             'std_db_pca.db'))
        else:
            self.database = load_database(
                os.path.join(application_path, 'std_db_pca.db'))

    def firstTimeStart(self, savename):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Information)
        msg.setText(
            "Please select a directory to search for images in. This will then build the search database."
        )
        msg.setWindowTitle("Setup")
        msg.setStandardButtons(QMessageBox.Ok)
        if msg.exec_():
            dir = None
            while not dir:
                dir = QFileDialog.getExistingDirectory(
                    self, 'Choose Search Root', QDir.homePath(),
                    QFileDialog.ShowDirsOnly)
            paths = get_image_paths(dir)
            self.progresswindow = self.make_progress_window(len(paths))
            self.progresswindow.show()
            graph = tf.get_default_graph()
            build_thread = Build_Thread(self.encoder, paths, graph, self)
            build_thread.processed.connect(self.update_progress)
            build_thread.finished.connect(self.set_database)
            build_thread.finished.connect(
                lambda db: save_database(db, savename))
            build_thread.close.connect(self.progresswindow.close)
            build_thread.close.connect(lambda: self.build_message(savename))
            build_thread.start()

    def build_message(self, savename=None):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Information)
        msg.setText("Database complete!")
        if not savename:
            msg.setInformativeText(
                "Don't forget to save your database to avoid having to rebuild it upon program restart."
            )
        else:
            msg.setInformativeText(
                "The database was saved as {} and will be loaded as default on all subsequent program starts."
                .format(savename))
        msg.setWindowTitle("Completed")
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def make_progress_window(self, max):
        window = QDialog(self)
        window.setWindowTitle("Building Search Database")
        self.progress = QProgressBar(window)
        self.progress.setMaximum(max)
        self.progresslabel = QLabel(window)
        self.progresslabel.setText("Processed: 0 of {}".format(max))
        layout = QVBoxLayout()
        window.setLayout(layout)
        layout.addWidget(self.progresslabel)
        layout.addWidget(self.progress)
        window.setModal(Qt.ApplicationModal)
        return window

    def update_progress(self, prog, max):
        self.progresslabel.setText("Processed: {} of {}".format(prog, max))
        self.progress.setValue(prog)

    def set_database(self, db):
        self.database = db

    def load_image(self):
        f, _ = QFileDialog.getOpenFileName(
            self, 'Load Image', QDir.homePath(),
            "Image Files (*.jpg *.jpeg *.png *.tga *.bmp)")
        if f:
            self.search_img_path = f
            self.searchlabel.setText('Query Image: {}'.format(
                os.path.basename(f)))
            if not self.button_search.isEnabled():
                self.button_search.setEnabled(True)

    def search(self):
        if self.search_img_path:
            #start = timer()
            self.lastSearchQuery = self.search_img_path
            if not self.button_save_result.isEnabled():
                self.button_save_result.setEnabled(True)
            hits = similarity_search(self.search_img_path, self.database,
                                     self.encoder, self.spinbox.value())
            self.countlabel.setText('# of hits: {}'.format(str(len(hits))))
            #Update Table Model
            self.model.removeRows(0, self.model.rowCount())
            for hit in hits:
                if b'name' in hit.keys():
                    row = {
                        "name": hit[b'name'],
                        "path": hit[b'path'],
                        "distance": hit['distance']
                    }
                else:
                    row = {
                        "name": hit['name'],
                        "path": hit['path'],
                        "distance": hit['distance']
                    }
                self.model.appendRow(row)
            #end = timer()
            #print('Search Time: {} seconds'.format(end - start))

    def save_search(self):
        filename, _ = QFileDialog.getSaveFileName(self, 'Save Result',
                                                  QDir.currentPath(),
                                                  'Text files (*.txt)')
        if filename:
            with open(filename, "w") as file:
                file.write("Query image: {}\n\n".format(self.lastSearchQuery))
                for i in range(self.model.rowCount()):
                    file.write("Name: {}, Path: {}, Distance: {}\n".format(
                        self.model.results[i]['name'],
                        self.model.results[i]['path'],
                        self.model.results[i]['distance']))

    def switch_NN(self, b):
        if b.text() == 'PCA' and b.isChecked():
            reset_tf_session()
            self.encoder = build_pca_autoencoder()
            self.database = load_database(
                os.path.join(application_path, 'std_db_pca.db'))
            self.search_img_path = None
            self.searchlabel.setText('Query Image:')
            self.button_search.setEnabled(False)
        elif b.text() == 'Convolutional' and b.isChecked():
            reset_tf_session()
            self.encoder = build_deep_autoencoder()
            if os.path.isfile(os.path.join(application_path,
                                           'std_db_deep.db')):
                self.database = load_database(
                    os.path.join(application_path, 'std_db_deep.db'))
            else:
                self.firstTimeStart(
                    os.path.join(application_path, 'std_db_deep.db'))
            self.search_img_path = None
            self.searchlabel.setText('Query Image:')
            self.button_search.setEnabled(False)
        else:
            pass

    def load_weights(self):
        filename, _ = QFileDialog.getOpenFileName(
            self, 'Load Weights', QDir.currentPath(),
            'Neural Network Weights (*.h5)')
        if filename:
            load_NN_weights(self.encoder, filename)

    def build_DB(self):
        dlg = QFileDialog()
        dlg.setFileMode(QFileDialog.Directory)
        if dlg.exec_():
            #start = timer()
            dirs = dlg.selectedFiles()
            paths = get_image_paths(dirs[0])
            self.progresswindow = self.make_progress_window(len(paths))
            self.progresswindow.show()
            graph = tf.get_default_graph()
            build_thread = Build_Thread(self.encoder, paths, graph, self)
            build_thread.processed.connect(self.update_progress)
            build_thread.finished.connect(self.set_database)
            build_thread.close.connect(self.progresswindow.close)
            build_thread.close.connect(self.build_message)
            #build_thread.close.connect(lambda : print(timer() - start))
            build_thread.start()

    def load_DB(self):
        dbname, _ = QFileDialog.getOpenFileName(self, 'Load Database',
                                                QDir.currentPath(),
                                                "Database files (*.db)")
        if dbname:
            self.database = load_database(dbname)

    def save_DB(self):
        filename, _ = QFileDialog.getSaveFileName(self, 'Save Database',
                                                  QDir.currentPath(),
                                                  'Database files (*.db)')
        if filename:
            save_database(self.database, filename, True)