示例#1
0
class DistanceGroup(DModule, QtWidgets.QGroupBox):
	
	calculate = QtCore.Signal()
	delete = QtCore.Signal()
	
	def __init__(self, view):
		
		self.view = view
		self.model = view.model
		
		DModule.__init__(self)
		QtWidgets.QGroupBox.__init__(self, "Distance")
		
		self.setLayout(QtWidgets.QVBoxLayout())
		
		self.calculate_button = Button("Calculate Distances", self.on_calculate)
		self.delete_button = Button("Delete Distances", self.on_delete)
		
		self.layout().addWidget(self.calculate_button)
		self.layout().addWidget(self.delete_button)
		
		self.update()
		
		self.connect_broadcast(Broadcasts.STORE_LOADED, self.on_data_source_changed)
		self.connect_broadcast(Broadcasts.STORE_DATA_SOURCE_CHANGED, self.on_data_source_changed)
		self.connect_broadcast(Broadcasts.STORE_DATA_CHANGED, self.on_data_changed)
	
	def update(self):
		
		self.calculate_button.setEnabled(self.model.has_samples() and (not self.model.has_distance()))
		self.delete_button.setEnabled(self.model.has_samples() and self.model.has_distance())
	
	@QtCore.Slot()
	def on_calculate(self):
		
		self.calculate.emit()
	
	@QtCore.Slot()
	def on_delete(self):
		
		self.delete.emit()
	
	def on_data_source_changed(self, *args):
		
		self.update()
	
	def on_data_changed(self, *args):
		
		self.update()
示例#2
0
class DescriptorGroup(DModule, QtWidgets.QGroupBox):

    load_data = QtCore.Signal()
    cluster_classes_changed = QtCore.Signal()

    def __init__(self, view):

        self.view = view
        self.model = view.model

        DModule.__init__(self)
        QtWidgets.QGroupBox.__init__(self, "Class / Descriptors")

        self.setLayout(QtWidgets.QVBoxLayout())

        self.sample_class_combo = Combo(self.on_sample_class_changed)
        self.cluster_class_combo = Combo(self.on_cluster_class_changed,
                                         editable=True)
        self.node_class_combo = Combo(self.on_node_class_changed,
                                      editable=True)

        classes_frame = QtWidgets.QFrame()
        classes_frame.setLayout(QtWidgets.QFormLayout())
        classes_frame.layout().setContentsMargins(0, 0, 0, 0)

        classes_frame.layout().addRow(QtWidgets.QLabel("Sample Class:"),
                                      self.sample_class_combo)
        classes_frame.layout().addRow(QtWidgets.QLabel("Cluster Class:"),
                                      self.cluster_class_combo)
        classes_frame.layout().addRow(QtWidgets.QLabel("Node Class:"),
                                      self.node_class_combo)

        self.descriptors_frame = QtWidgets.QFrame()
        self.descriptors_frame.setLayout(QtWidgets.QFormLayout())
        self.descriptors_frame.layout().setContentsMargins(0, 0, 0, 0)
        self.descriptors_frame.setVisible(False)

        self.load_data_button = Button("Load Data", self.on_load_data)
        self.load_descr_button = Button("Load Descriptors...",
                                        self.on_load_descriptors)
        button_frame = QtWidgets.QFrame()
        button_frame.setLayout(QtWidgets.QHBoxLayout())
        button_frame.layout().setContentsMargins(0, 0, 0, 0)
        button_frame.layout().addWidget(self.load_data_button)
        button_frame.layout().addWidget(self.load_descr_button)

        descriptor_box = QtWidgets.QFrame()
        descriptor_box.setLayout(QtWidgets.QVBoxLayout())
        descriptor_box.layout().setContentsMargins(0, 0, 0, 0)
        self.descriptor_header_button = HeaderButton("Descriptors")
        descriptor_box.layout().addWidget(self.descriptor_header_button)
        descriptor_box.layout().addWidget(self.descriptors_frame)
        self.descriptor_header_button.clicked.connect(
            self.on_descriptors_clicked)

        self.layout().addWidget(classes_frame)
        self.layout().addWidget(descriptor_box)
        self.layout().addWidget(button_frame)

        self.load_descriptors()
        self.update()

        self.connect_broadcast(Broadcasts.STORE_LOADED, self.on_store_changed)
        self.connect_broadcast(Broadcasts.STORE_DATA_SOURCE_CHANGED,
                               self.on_store_changed)
        self.connect_broadcast(Broadcasts.STORE_DATA_CHANGED,
                               self.on_store_changed)

    def load_descriptors(self, descriptors=None):

        sample_cls = self.sample_class_combo.get_value()
        self.model.load_lap_descriptors(descriptors, sample_cls)

        for row in range(self.descriptors_frame.layout().rowCount())[::-1]:
            self.descriptors_frame.layout().removeRow(row)

        if self.model.lap_descriptors is not None:
            for name in self.model.lap_descriptors:
                self.descriptors_frame.layout().addRow(
                    QtWidgets.QLabel("   %s" % (name)),
                    QtWidgets.QLabel(".".join(
                        self.model.lap_descriptors[name])))

    def update(self):

        self.load_data_button.setEnabled(
            self.model.is_connected()
            and (self.model.lap_descriptors is not None))

    def update_classes(self):

        if self.model.is_connected():
            descriptors = set(self.model.descriptor_names)
            classes = natsorted([
                name for name in self.model.classes if name not in descriptors
            ])
            self.sample_class_combo.set_values(classes, "Sample")
            self.cluster_class_combo.set_values(classes, "CMCluster")
            self.node_class_combo.set_values(classes, "CMNode")
        else:
            self.sample_class_combo.set_values([])
            self.cluster_class_combo.set_values([])
            self.node_class_combo.set_values([])
        self.model.cluster_class = self.cluster_class_combo.get_value()
        self.model.node_class = self.node_class_combo.get_value()

    def on_store_changed(self, *args):

        self.update_classes()
        self.load_descriptors()
        self.update()

    @QtCore.Slot()
    def on_descriptors_clicked(self):

        visible = self.descriptors_frame.isVisible()
        if visible:
            self.descriptor_header_button.setArrowType(QtCore.Qt.RightArrow)
        else:
            self.descriptor_header_button.setArrowType(QtCore.Qt.DownArrow)
        self.descriptors_frame.setVisible(not visible)

    @QtCore.Slot()
    def on_sample_class_changed(self):

        self.load_descriptors()
        self.update()
        self.cluster_classes_changed.emit()

    @QtCore.Slot()
    def on_node_class_changed(self):

        self.model.node_class = self.node_class_combo.get_value()
        self.update()
        self.cluster_classes_changed.emit()

    @QtCore.Slot()
    def on_cluster_class_changed(self):

        self.model.cluster_class = self.cluster_class_combo.get_value()
        self.update()
        self.cluster_classes_changed.emit()

    @QtCore.Slot()
    def on_load_data(self):

        self.load_data.emit()

    @QtCore.Slot()
    def on_load_descriptors(self):

        url, format = QtWidgets.QFileDialog.getOpenFileUrl(
            self.view, caption="Import Descriptors", filter="(*.txt)")
        url = str(url.toString())
        if not url:
            return
        path = as_path(url)
        if path is None:
            return
        with open(path, "r") as f:
            _, descriptors = json.load(f)
        self.load_descriptors(descriptors)
        self.update()
示例#3
0
文件: View.py 项目: COPES3D/CeraMatch
class View(DModule, QtWidgets.QMainWindow):

    MODE_IDS = 0x00000001
    MODE_DISTANCE_MIN = 0x00000002
    MODE_DISTANCE_MAX = 0x00000004
    MODE_CLUSTER = 0x00000008

    def __init__(self):

        self.model = None
        self.registry = None
        self.mode = None
        self._loaded = False

        DModule.__init__(self)
        QtWidgets.QMainWindow.__init__(self)

        self.model = Model(self)

        self.setWindowTitle("CeraMatch")
        self.setWindowIcon(QtGui.QIcon("res\cm_icon.svg"))
        self.setStyleSheet("QPushButton {padding: 5px; min-width: 100px;}")

        self.central_widget = QtWidgets.QWidget(self)
        self.central_widget.setLayout(QtWidgets.QVBoxLayout())
        self.central_widget.layout().setContentsMargins(0, 0, 0, 0)
        self.setCentralWidget(self.central_widget)

        self.splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)

        self.central_widget.layout().addWidget(self.splitter)

        self.registry = Registry("Deposit")
        self.image_view = ImageView(self)
        self.footer_frame = FooterFrame(self)
        self.descriptor_group = DescriptorGroup(self)
        self.sort_group = SortGroup(self)
        self.cluster_group = ClusterGroup(self)
        self.menu = Menu(self)
        self.toolbar = ToolBar(self)
        self.statusbar = StatusBar(self)

        self.calculate_button = Button("Calculate Distances",
                                       self.on_calculate)
        self.calculate_button.setEnabled(False)

        self.left_frame = QtWidgets.QFrame(self)
        self.left_frame.setLayout(QtWidgets.QVBoxLayout())
        self.left_frame.layout().setContentsMargins(10, 10, 0, 10)

        self.right_frame = QtWidgets.QFrame(self)
        self.right_frame.setLayout(QtWidgets.QVBoxLayout())
        self.right_frame.layout().setContentsMargins(0, 0, 0, 0)

        self.splitter.addWidget(self.left_frame)
        self.splitter.addWidget(self.right_frame)

        self.left_frame.layout().addWidget(self.descriptor_group)
        group = QtWidgets.QGroupBox("Calculate")
        group.setLayout(QtWidgets.QVBoxLayout())
        group.layout().addWidget(self.calculate_button)
        self.left_frame.layout().addWidget(group)
        self.left_frame.layout().addWidget(self.sort_group)
        self.left_frame.layout().addWidget(self.cluster_group)
        self.left_frame.layout().addStretch()

        self.right_frame.layout().addWidget(self.image_view)
        self.right_frame.layout().addWidget(self.footer_frame)

        self.setStatusBar(self.statusbar)

        self._loaded = True

        self.setGeometry(100, 100, 1024, 768)

        self.on_samples()

        self.footer_frame.slider_zoom.setValue(100)

        self.connect_broadcast(Broadcasts.VIEW_ACTION, self.on_update)
        self.connect_broadcast(Broadcasts.STORE_LOCAL_FOLDER_CHANGED,
                               self.on_update)
        self.connect_broadcast(Broadcasts.STORE_SAVED, self.on_update)
        self.connect_broadcast(Broadcasts.STORE_LOADED, self.on_update)
        self.connect_broadcast(Broadcasts.STORE_DATA_SOURCE_CHANGED,
                               self.on_update)
        self.connect_broadcast(Broadcasts.STORE_DATA_CHANGED, self.on_update)

    def get_selected(self):
        # returns [[sample_id, DResource, label, value, index], ...]

        return self.image_view.get_selected()

    def update(self):

        if not hasattr(self, "descriptor_group"):
            return

        self.descriptor_group.update()
        self.sort_group.update()
        self.cluster_group.update()
        self.footer_frame.update()
        self.toolbar.update()
        self.image_view.update_()

        selected = self.get_selected()

        self.calculate_button.setEnabled(self.model.is_connected()
                                         and not self.model.has_distances())

        if selected:
            if self.mode == self.MODE_DISTANCE_MIN:
                text = "Distance: %s, Sample ID: %s" % (selected[0].value,
                                                        selected[0].id)
            else:
                cluster = selected[0].cluster
                levels = self.image_view.get_selected_level()
                if levels:
                    cluster = ".".join(cluster.split(".")[:levels[0]])
                if cluster:
                    text = "Cluster: %s, Leaf: %s, Sample ID: %s" % (
                        cluster, selected[0].value, selected[0].id)
                else:
                    text = "Sample ID: %s" % (selected[0].id)
            self.statusbar.message(text)

    def reload_samples(self):

        self.model.load_samples()
        self.update()
        self.on_samples()

    def on_update(self, *args):

        self.update()

    def on_set_datasource(self, *args):

        self.reload_samples()

    def on_calculate(self, *args):

        self.model.calc_distances()

    def on_samples(self, *args):

        self.mode = self.MODE_IDS
        self.model.sort_by_ids()

    def on_distance_max(self, *args):

        self.mode = self.MODE_DISTANCE_MAX
        self.model.sort_by_distmax()

    def on_distance_min(self, *args):

        selected = self.get_selected()
        if selected:
            sample_id = selected[0].id
        elif isinstance(self.model.samples[0].value, float):
            sample_id = self.model.samples[0].id
        else:
            return
        self.mode = self.MODE_DISTANCE_MIN
        self.model.sort_by_distance(sample_id)

    def on_cluster(self, *args):

        self.mode = self.MODE_CLUSTER

        if self.model.has_clusters():
            self.model.update_clusters()

    def on_auto_cluster(self, *args):

        self.mode = self.MODE_CLUSTER
        self.model.auto_cluster()

    def on_split_cluster(self, *args):

        selected = self.image_view.get_selected()
        if len(selected) != 1:
            return
        cluster = selected[0].cluster
        if cluster is None:
            return
        self.model.split_cluster(cluster, selected[0])

    def on_join_parent(self, *args):

        selected = self.image_view.get_selected()
        if not selected:
            return
        self.mode = self.MODE_CLUSTER

        clusters = set()
        for sample in selected:
            if sample.cluster is None:
                continue
            clusters.add(sample.cluster)
        if clusters:
            for cluster in clusters:
                self.model.join_cluster_to_parent(cluster, selected[0])

    def on_join_children(self, *args):

        selected = self.image_view.get_selected()
        if not selected:
            return
        self.mode = self.MODE_CLUSTER

        clusters = set()
        for sample in selected:
            if sample.cluster is None:
                continue
            clusters.add(sample.cluster)
        if clusters:

            levels = self.image_view.get_selected_level()
            if not levels:
                return
            level = max(levels)
            for cluster in clusters:
                self.model.join_children_to_cluster(cluster, level,
                                                    selected[0])
        else:
            return

    def on_manual_cluster(self, *args):

        selected = self.image_view.get_selected()
        if not selected:
            return
        self.model.manual_cluster(selected, selected[0])

    def on_clear_clusters(self, *args):

        self.model.clear_clusters()
        self.model.sort_by_ids()

    def on_reload(self, *args):

        if self.mode is None:
            return
        if self.mode == self.MODE_IDS:
            self.on_samples()
        elif self.mode == self.MODE_DISTANCE_MIN:
            self.on_distance_min()
        elif self.mode == self.MODE_DISTANCE_MAX:
            self.on_distance_max()
        elif self.mode == self.MODE_CLUSTER:
            self.on_cluster()

    def on_prev(self, *args):

        self.model.browse_distmax(-1)

    def on_next(self, *args):

        self.model.browse_distmax(1)

    def on_zoom(self, value):

        self.image_view.set_thumbnail_size(value)

    def on_drop(self, src_ids, tgt_id):

        self.model.add_to_cluster(src_ids, tgt_id)

    def closeEvent(self, event):

        if not self.model.is_saved():
            reply = QtWidgets.QMessageBox.question(
                self, "Exit", "Save changes to database?",
                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
                | QtWidgets.QMessageBox.Cancel)
            if reply == QtWidgets.QMessageBox.Yes:
                self.toolbar.on_save()
            elif reply == QtWidgets.QMessageBox.No:
                pass
            else:
                event.ignore()
                return

        self.model.on_close()
示例#4
0
class SortGroup(QtWidgets.QGroupBox):
    def __init__(self, view):

        self.view = view
        self.model = view.model

        QtWidgets.QGroupBox.__init__(self, "Sort")

        self.setLayout(QtWidgets.QVBoxLayout())

        self.samples_button = Button("Sort by Sample IDs",
                                     self.view.on_samples)
        self.samples_button.setEnabled(False)
        self.distance_max_button = Button("Sort by Max Distance",
                                          self.view.on_distance_max)
        self.distance_max_button.setEnabled(False)
        self.distance_min_button = Button("Sort by Min Distance",
                                          self.view.on_distance_min)
        self.distance_min_button.setEnabled(False)
        self.cluster_button = Button("Sort by Clustering",
                                     self.view.on_cluster)
        self.cluster_button.setEnabled(False)

        self.layout().addWidget(self.samples_button)
        self.layout().addWidget(self.distance_max_button)
        self.layout().addWidget(self.distance_min_button)
        self.layout().addWidget(self.cluster_button)

    def update(self):

        selected = self.view.get_selected()

        self.samples_button.setEnabled(self.model.is_connected())
        self.distance_max_button.setEnabled(self.model.has_distances())
        self.distance_min_button.setEnabled(
            self.model.has_distances()
            and ((len(selected) > 0) or
                 ((len(self.model.samples) > 0)
                  and isinstance(self.model.samples[0].value, float))))
        self.cluster_button.setEnabled(self.model.has_clusters())
示例#5
0
class ClusterGroup(DModule, QtWidgets.QGroupBox):

    cluster = QtCore.Signal()
    update_tree = QtCore.Signal()
    add_cluster = QtCore.Signal()
    delete = QtCore.Signal()

    def __init__(self, view):

        self.view = view
        self.model = view.model

        DModule.__init__(self)
        QtWidgets.QGroupBox.__init__(self, "Clustering")

        self.setLayout(QtWidgets.QVBoxLayout())

        self.cluster_button = Button("Auto Cluster", self.on_cluster)
        self.n_clusters_combo = Combo(self.on_n_clusters_changed)
        self.limit_edit = LineEdit("0.68")
        self.n_samples_label = QtWidgets.QLabel("")
        self.n_clusters_label = QtWidgets.QLabel("")
        self.update_tree_button = Button("Update Tree", self.on_update_tree)
        self.add_cluster_button = Button("Make Cluster", self.on_add_cluster)
        self.delete_button = Button("Clear Clusters", self.on_delete)

        form_frame = QtWidgets.QFrame()
        form_frame.setLayout(QtWidgets.QFormLayout())
        form_frame.layout().setContentsMargins(0, 0, 0, 0)
        form_frame.layout().addRow(QtWidgets.QLabel("N Clusters:"),
                                   self.n_clusters_combo)
        form_frame.layout().addRow(QtWidgets.QLabel("Dist. Limit:"),
                                   self.limit_edit)
        form_frame.layout().addRow(QtWidgets.QLabel("Samples:"),
                                   self.n_samples_label)
        form_frame.layout().addRow(QtWidgets.QLabel("Clusters Found:"),
                                   self.n_clusters_label)

        self.layout().addWidget(self.cluster_button)
        self.layout().addWidget(form_frame)
        self.layout().addWidget(self.update_tree_button)
        self.layout().addWidget(self.add_cluster_button)
        self.layout().addWidget(self.delete_button)

        self.update()

        self.connect_broadcast(Broadcasts.STORE_LOADED,
                               self.on_data_source_changed)
        self.connect_broadcast(Broadcasts.STORE_DATA_SOURCE_CHANGED,
                               self.on_data_source_changed)
        self.connect_broadcast(Broadcasts.STORE_DATA_CHANGED,
                               self.on_data_changed)

    def get_limits(self):

        try:
            n_clusters = int(self.n_clusters_combo.get_value())
        except:
            n_clusters = None
        try:
            limit = float(self.limit_edit.text())
        except:
            limit = 0.68
        return n_clusters, limit

    def update(self):

        self.cluster_button.setEnabled(self.model.has_cluster_classes()
                                       and self.model.has_distance())
        self.n_clusters_combo.setEnabled(self.model.has_distance())
        self.limit_edit.setEnabled(
            self.model.has_distance()
            and (self.n_clusters_combo.get_value() == "By limit"))
        self.n_samples_label.setText(str(len(self.model.sample_ids)))
        self.update_tree_button.setEnabled(self.model.has_samples())
        self.add_cluster_button.setEnabled(
            len(self.view.graph_view.get_selected()) > 0)
        self.delete_button.setEnabled(self.model.has_cluster_classes()
                                      and self.model.has_clusters())

    def update_n_clusters(self):

        values = ["By limit"]
        if self.model.has_distance():
            values += list(range(2, len(self.model.sample_ids) + 1))
        self.n_clusters_combo.set_values(values)
        self.update()

    def update_clusters_found(self, n_clusters):

        if str(n_clusters).isnumeric():
            self.n_clusters_label.setText(str(n_clusters))
        else:
            self.n_clusters_label.setText("")

    @QtCore.Slot()
    def on_cluster(self):

        self.cluster.emit()

    @QtCore.Slot()
    def on_update_tree(self):

        self.update_tree.emit()

    @QtCore.Slot()
    def on_add_cluster(self):

        self.add_cluster.emit()

    @QtCore.Slot()
    def on_delete(self):

        self.delete.emit()

    @QtCore.Slot()
    def on_n_clusters_changed(self):

        self.update()

    def on_data_source_changed(self, *args):

        self.update_n_clusters()
        self.update_clusters_found(None)
        self.update()

    def on_data_changed(self, *args):

        self.update()
示例#6
0
class DescriptorGroup(QtWidgets.QGroupBox):
    def __init__(self, view):

        self.view = view
        self.model = view.model
        self.changed = False

        QtWidgets.QGroupBox.__init__(self, "Class / Descriptors")

        self.setLayout(QtWidgets.QVBoxLayout())

        self.class_combo = Combo(self.on_changed)
        self.id_combo = Combo(self.on_changed)
        self.profile_combo = Combo(self.on_changed)
        self.radius_combo = Combo(self.on_changed)
        self.recons_combo = Combo(self.on_changed)

        self.load_button = Button("Load", self.on_load)

        form_frame = QtWidgets.QFrame()
        form_frame.setLayout(QtWidgets.QFormLayout())
        form_frame.layout().setContentsMargins(0, 0, 0, 0)

        cluster_label = QtWidgets.QLineEdit("CMCluster")
        cluster_label.setReadOnly(True)
        cluster_label.setSizePolicy(QtWidgets.QSizePolicy.Ignored,
                                    QtWidgets.QSizePolicy.Expanding)

        form_frame.layout().addRow(QtWidgets.QLabel("Sample Class:"),
                                   self.class_combo)
        form_frame.layout().addRow(QtWidgets.QLabel("Cluster Class:"),
                                   cluster_label)
        form_frame.layout().addRow(QtWidgets.QLabel("Sample Descriptors:"),
                                   None)
        form_frame.layout().addRow(QtWidgets.QLabel("Id:"), self.id_combo)
        form_frame.layout().addRow(QtWidgets.QLabel("Profile:"),
                                   self.profile_combo)
        form_frame.layout().addRow(QtWidgets.QLabel("Radius:"),
                                   self.radius_combo)
        form_frame.layout().addRow(QtWidgets.QLabel("Drawing:"),
                                   self.recons_combo)
        self.layout().addWidget(form_frame)
        self.layout().addWidget(self.load_button)

    def update(self):

        if self.model.is_connected():
            self.class_combo.set_values(
                natsorted(list(self.model.classes.keys())), "Sample")
            sample_cls = self.class_combo.get_value()
            if sample_cls:
                descriptors = self.model.classes[sample_cls].descriptors
                self.id_combo.set_values(descriptors, "Id")
                self.profile_combo.set_values(descriptors, "Profile")
                self.radius_combo.set_values(descriptors, "Radius")
                self.recons_combo.set_values(descriptors, "Reconstruction")

        self.load_button.setEnabled(self.model.is_connected() and self.changed
                                    and (self.get_values() is not None))

    def get_values(self):
        # return [Sample Class, ID Descriptor, Profile Descriptor, Radius Descriptor, Reconstruction Descriptor]

        values = []
        controls = [
            self.class_combo, self.id_combo, self.profile_combo,
            self.radius_combo, self.recons_combo
        ]
        for control in controls:
            value = control.get_value()
            if value:
                values.append(value)
        if len(values) == len(controls):
            return values
        return None

    def on_changed(self, *args):

        self.changed = True
        self.update()

    def on_load(self, *args):

        self.changed = False
        self.view.reload_samples()
示例#7
0
class ClusterGroup(QtWidgets.QGroupBox):
    def __init__(self, view):

        self.view = view
        self.model = view.model
        self.sliders = {}

        QtWidgets.QGroupBox.__init__(self, "Cluster")

        self.setLayout(QtWidgets.QVBoxLayout())

        self.autoclust_button = Button("Auto Cluster",
                                       self.view.on_auto_cluster)
        self.autoclust_button.setEnabled(False)
        self.split_button = Button("Auto Split", self.view.on_split_cluster)
        self.split_button.setEnabled(False)
        self.join_parent_button = Button("Join to Parent",
                                         self.view.on_join_parent)
        self.join_parent_button.setEnabled(False)
        self.join_children_button = Button("Join Children",
                                           self.view.on_join_children)
        self.join_children_button.setEnabled(False)
        self.manual_button = Button("Create Manual",
                                    self.view.on_manual_cluster)
        self.manual_button.setEnabled(False)
        self.clear_button = Button("Clear All", self.view.on_clear_clusters)
        self.clear_button.setEnabled(False)

        self.layout().addWidget(self.autoclust_button)
        self.layout().addWidget(self.split_button)
        self.layout().addWidget(self.join_parent_button)
        self.layout().addWidget(self.join_children_button)
        self.layout().addWidget(self.manual_button)
        self.layout().addWidget(self.clear_button)

    def update(self):

        selected = self.view.get_selected()

        has_cluster = ((len(selected) > 0) and (selected[0].has_cluster()))

        self.autoclust_button.setEnabled(self.model.has_distances())
        self.split_button.setEnabled(len(selected) == 1)

        self.join_parent_button.setEnabled(has_cluster)
        self.join_children_button.setEnabled(
            has_cluster and not self.view.image_view.is_list())

        self.manual_button.setEnabled(len(selected) > 0)

        self.clear_button.setEnabled(self.model.has_clusters())