From e17a6ba381cefec1f7ea50b8e348cb986a7ea14c Mon Sep 17 00:00:00 2001 From: "benjamin.jakimow@geo.hu-berlin.de" <q8DTkxUg-BB> Date: Mon, 3 Apr 2017 18:06:42 +0200 Subject: [PATCH] getTextColorWithContrast(QColor) ClassInfo(QObject).setLabel(label) ClassInfo(QObject).__str__() changes to ClassificationScheme(QObject) (clear, saveToRaster) ClassificationSchemeTableModel, ClassificationWidgetDelegates --- timeseriesviewer/classificationscheme.py | 313 ++++++++++++++++++++--- 1 file changed, 272 insertions(+), 41 deletions(-) diff --git a/timeseriesviewer/classificationscheme.py b/timeseriesviewer/classificationscheme.py index 5596e275..0359a007 100644 --- a/timeseriesviewer/classificationscheme.py +++ b/timeseriesviewer/classificationscheme.py @@ -14,27 +14,47 @@ from timeseriesviewer.ui.widgets import loadUIFormClass load = lambda p : loadUIFormClass(jp(DIR_UI,p)) -class ClassInfo(QObject): - def __init__(self, name=None, color=None): - self.mName = '' +def getTextColorWithContrast(c): + assert isinstance(c, QColor) + if c.lightness() < 0.5: + return QColor('white') + else: + return QColor('black') + + +class ClassInfo(QObject): + sigSettingsChanged = pyqtSignal() + def __init__(self, label=0, name='unclassified', color=None): + super(ClassInfo, self).__init__() + self.mName = name + self.mLabel = label self.mColor = QColor('black') - if name: - self.setName(name) if color: self.setColor(color) + def setLabel(self, label): + assert isinstance(label, int) + assert label >= 0 + self.mLabel = label + self.sigSettingsChanged.emit() + def setColor(self, color): assert isinstance(color, QColor) self.mColor = color + self.sigSettingsChanged.emit() def setName(self, name): assert isinstance(name, str) self.mName = name + self.sigSettingsChanged.emit() def clone(self): return ClassInfo(name=self.mName, color=self.mColor) + def __str__(self): + return '{} "{}"'.format(self.mLabel,self.mName) + class ClassificationScheme(QObject): @staticmethod def fromRasterImage(path, bandIndex=None): @@ -69,14 +89,30 @@ class ClassificationScheme(QObject): def fromVectorFile(self, path, fieldClassName='classname', fieldClassColor='classColor'): pass - def clear(self): - del self.classes[:] + sigClassRemoved = pyqtSignal(ClassInfo) + sigClassAdded = pyqtSignal(ClassInfo) def __init__(self): super(ClassificationScheme, self).__init__() self.classes = [] + def clear(self): + removed = self.classes[:] + del self.classes[:] + + + def __getitem__(self, slice): + return self.classes[slice] + + def __delitem__(self, slice): + classes = self[slice] + for c in classes: + self.removeClass(c) + + def __contains__(self, item): + return item in self.classes + def __len__(self): return len(self.classes) @@ -85,36 +121,108 @@ class ClassificationScheme(QObject): def removeClass(self, c): assert c in self.classes + self.classes.remove(c) + self.sigClassRemoved.emit(c) def addClass(self, c, index=None): assert isinstance(c, ClassInfo) if index is None: index = len(self.classes) + c.setLabel(index) self.classes.insert(index, c) + self.sigClassAdded.emit(c) + + def saveToRaster(self, path, bandIndex=0): + + ds = gdal.Open(path) + assert ds is not None + assert ds.RasterCount < bandIndex + band = ds.GetRasterBand(bandIndex+1) + ct = gdal.ColorTable() + cat = [] + for i, classInfo in enumerate(self.classes): + c = classInfo.mColor + cat.append(classInfo.mName) + assert isinstance(c, QColor) + rgba = (c.red(), c.green(), c.blue(), c.alpha()) + ct.SetColorEntry(i, *rgba) + + band.SetColorTable(ct) + band.SetCategoryNames(cat) + + ds = None + + + def toString(self, sep=';'): + lines = [sep.join(['class_value', 'class_name', 'R', 'G', 'B', 'A'])] + for classInfo in self.classes: + c = classInfo.mColor + info = [classInfo.mValue, classInfo.mName, c.red(), c.green(), c.blue(), c.alpha()] + info = ['{}'.format(v) for v in info] + + lines.append(sep.join(info)) + return '\n'.join(lines) + + def saveToCsv(self, path, sep=';'): + lines = self.toString(sep=sep) + file = open(path, 'w') + file.write(lines) + file.close() + + class ClassificationSchemeTableModel(QAbstractTableModel): - columnNames = ['label', 'name', 'color'] - def __init__(self, parent=None): + + def __init__(self, scheme, parent=None): + self.cLABEL = 'Label' + self.cNAME = 'Name' + self.cCOLOR = 'Color' + self.columnNames = [self.cLABEL, self.cNAME, self.cCOLOR] + assert isinstance(scheme, ClassificationScheme) super(ClassificationSchemeTableModel, self).__init__(parent) - self.scheme = ClassificationScheme() + self.valLabel = QIntValidator(0, 99999) - def loadClassesFromImage(self, path, append=True): + self.scheme = scheme + #self.scheme.sigClassRemoved.connect(lambda : self.reset()) + #self.scheme.sigClassAdded.connect(self.onClassAdded) + + #self.modelReset.emit() + + #idx = self.getIndexFromClassInfo(c) + #self.beginInsertRows(idx.parent(), idx.row(), 1) + #self.endInsertRows() + + def removeClass(self, c): + idx = self.getIndexFromClassInfo(c) + if idx: + self.beginRemoveRows(idx.parent(), idx.row(), idx.row()) + self.scheme.removeClass(c) + self.endRemoveRows() - if not append: - for c in self.classes: - self.removeClass(c) + def insertClass(self, c, i=None): + if i is None: + i = len(self.scheme) + self.beginInsertRows(QModelIndex(), i, i) + self.scheme.addClass(c,i) + self.endInsertRows() + + + def clear(self): + self.beginRemoveRows(QModelIndex(), 0, self.rowCount()-1) + self.scheme.clear() + self.endRemoveRows() def rowCount(self, QModelIndex_parent=None, *args, **kwargs): return len(self.scheme) def columnCount(self, parent = QModelIndex()): - return len(self.columNames) + return len(self.columnNames) def getIndexFromClassInfo(self, classInfo): - return self.createIndex(self.scheme.index(classInfo),0) + return self.createIndex(self.scheme.classes.index(classInfo),0) def getClassInfoFromIndex(self, index): if index.isValid(): @@ -127,43 +235,61 @@ class ClassificationSchemeTableModel(QAbstractTableModel): if role is None or not index.isValid(): return None - columnName = self.columnames[index.column()] + columnName = self.columnNames[index.column()] classInfo = self.getClassInfoFromIndex(index) assert isinstance(classInfo, ClassInfo) value = None if role == Qt.DisplayRole: - if columnName == 'id': - value = index.row() - if columnName == 'name': + if columnName == self.cLABEL: + value = classInfo.mLabel + elif columnName == self.cNAME: value = classInfo.mName - elif columnName == 'color': - value = str(classInfo.mColor) + elif columnName == self.cCOLOR: + value = classInfo.mColor + else: + s = "" + if role == Qt.BackgroundRole: + if columnName == self.cCOLOR: + return QBrush(classInfo.mColor) + if role == Qt.ForegroundRole: + if columnName == self.cCOLOR: + return getTextColorWithContrast(classInfo.mColor) + + + if role == Qt.UserRole: + return classInfo return value def setData(self, index, value, role=None): if role is None or not index.isValid(): return None - columnName = self.columnames[index.column()] + columnName = self.columnNames[index.column()] classInfo = self.getClassInfoFromIndex(index) assert isinstance(classInfo, ClassInfo) - if role == Qt.EditRole and columnName == 'name': - if len(value) == 0: # do not accept empty strings - return False - classInfo.setName(str(value)) - return True - + if role == Qt.EditRole: + if columnName == self.cNAME and len(value) > 0: + # do not accept empty strings + classInfo.setName(str(value)) + return True + if columnName == self.cCOLOR and isinstance(value, QColor): + classInfo.setColor(value) + return True + if columnName == self.cLABEL and \ + self.valLabel.validate(value,0)[0] == QValidator.Acceptable: + classInfo.setLabel(int(value)) + return True return False def flags(self, index): if index.isValid(): - columnName = self.columnames[index.column()] + columnName = self.columnNames[index.column()] flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable - if columnName in ['name']: # allow check state + if columnName in [self.cLABEL, self.cNAME]: # allow check state flags = flags | Qt.ItemIsUserCheckable | Qt.ItemIsEditable return flags # return item.qt_flags(index.column()) @@ -173,31 +299,132 @@ class ClassificationSchemeTableModel(QAbstractTableModel): if Qt is None: return None if orientation == Qt.Horizontal and role == Qt.DisplayRole: - return self.columnames[col] + return self.columnNames[col] elif orientation == Qt.Vertical and role == Qt.DisplayRole: return col return None + +class ClassificationWidgetDelegates(QStyledItemDelegate): + + def __init__(self, tableView, parent=None): + assert isinstance(tableView, QTableView) + super(ClassificationWidgetDelegates, self).__init__(parent=parent) + self.tableView = tableView + self.tableView.doubleClicked.connect(self.onDoubleClick) + #self.tableView.model().rowsInserted.connect(self.onRowsInserted) + + def onDoubleClick(self, idx): + model = self.tableView.model() + classInfo = model.getClassInfoFromIndex(idx) + if idx.column() == model.columnNames.index(model.cCOLOR): + + w1 = QColorDialog(classInfo.mColor, self.tableView) + w1.exec_() + if w1.result() == QDialog.Accepted: + c = w1.getColor() + model.setData(idx, c, role=Qt.EditRole) + + + + def getColumnName(self, index): + assert index.isValid() + assert isinstance(index.model(), ClassificationSchemeTableModel) + return index.model().columnNames[index.column()] + + def createEditor(self, parent, option, index): + cname = self.getColumnName(index) + model = index.model() + assert isinstance(model, ClassificationSchemeTableModel) + w = None + if False and cname == model.cCOLOR: + classInfo = model.getClassInfoFromIndex(index) + w = QgsColorButton(parent, 'Class {}'.format(classInfo.mName)) + w.setColor(QColor(index.data())) + w.colorChanged.connect(lambda: self.commitData.emit(w)) + return w + + def setEditorData(self, editor, index): + cname = self.getColumnName(index) + model = index.model() + assert isinstance(model, ClassificationSchemeTableModel) + + classInfo = model.getClassInfoFromIndex(index) + assert isinstance(classInfo, ClassInfo) + if False and cname == model.cCOLOR: + lastColor = classInfo.mColor + assert isinstance(editor, QgsColorButton) + assert isinstance(lastColor, QColor) + editor.setColor(QColor(lastColor)) + editor.setText('{},{},{}'.format(lastColor.red(), lastColor.green(), lastColor.blue())) + + def setModelData(self, w, model, index): + cname = self.getColumnName(index) + model = index.model() + assert isinstance(model, ClassificationSchemeTableModel) + + if False and cname == model.cCOLOR: + assert isinstance(w, QgsColorButton) + if index.data() != w.color(): + model.setData(index, w.color(), Qt.EditRole) + class ClassificationSchemeWidget(QWidget, load('classificationscheme.ui')): + + def __init__(self, parent=None, classificationScheme=None): super(ClassificationSchemeWidget, self).__init__(parent) self.setupUi(self) self.mScheme = ClassificationScheme() + if classificationScheme is not None: self.setClassificationScheme(classificationScheme) - self.tableViewModel = ClassificationSchemeTableModel(self) - self.tableClassificationScheme.setModel(self.tableViewModel) + self.schemeModel = ClassificationSchemeTableModel(self.mScheme, self) + + self.tableClassificationScheme.verticalHeader().setMovable(True) + self.tableClassificationScheme.verticalHeader().setDragEnabled(True) + self.tableClassificationScheme.verticalHeader().setDragDropMode(QAbstractItemView.InternalMove) + self.tableClassificationScheme.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents) + self.tableClassificationScheme.setModel(self.schemeModel) + self.tableClassificationScheme.doubleClicked.connect(self.onTableDoubleClick) + self.selectionModel = QItemSelectionModel(self.schemeModel) + self.selectionModel.selectionChanged.connect(self.onSelectionChanged) + self.onSelectionChanged() #enable/disabel widgets depending on a selection + self.tableClassificationScheme.setSelectionModel(self.selectionModel) + + #self.delegate = ClassificationWidgetDelegates(self.tableClassificationScheme) + #self.tableClassificationScheme.setItemDelegateForColumn(2, self.delegate) + self.btnLoadClasses.clicked.connect(self.loadClasses) self.btnRemoveClasses.clicked.connect(self.removeSelectedClasses) - self.btnAddClasses.clicked.connect(self.addClasses) + self.btnAddClasses.clicked.connect(lambda:self.createClasses(1)) + + def onTableDoubleClick(self, idx): + model = self.tableClassificationScheme.model() + classInfo = model.getClassInfoFromIndex(idx) + if idx.column() == model.columnNames.index(model.cCOLOR): - def addClasses(self, n): + c = QColorDialog.getColor(classInfo.mColor, self.tableClassificationScheme, \ + 'Set class color') + model.setData(idx, c, role=Qt.EditRole) + def onSelectionChanged(self, *args): + self.btnRemoveClasses.setEnabled(self.selectionModel is not None and + len(self.selectionModel.selectedRows()) > 0) + + def createClasses(self, n): for i in range(n): c = ClassInfo(name = '<empty>', color = QColor('red')) - self.mScheme.addClass(c) + self.schemeModel.insertClass(c) + + + def removeSelectedClasses(self): + model = self.tableClassificationScheme.model() + indices = reversed(self.selectionModel.selectedRows()) + classes = [self.schemeModel.getClassInfoFromIndex(idx) for idx in indices] + for c in classes: + self.schemeModel.removeClass(c) def loadClasses(self, *args): @@ -211,14 +438,17 @@ class ClassificationSchemeWidget(QWidget, load('classificationscheme.ui')): def appendClassificationScheme(self, classificationScheme): assert isinstance(classificationScheme, ClassificationScheme) for c in classificationScheme: - self.mScheme.addClass(c) + self.schemeModel.insertClass(c.clone()) def setClassificationScheme(self, classificationScheme): assert isinstance(classificationScheme, ClassificationScheme) - self.mScheme.classes[:] + self.schemeModel.clear() self.appendClassificationScheme(classificationScheme) + def classificationScheme(self): + return self.mScheme + class ClassificationSchemeDialog(QgsDialog): @@ -234,7 +464,7 @@ class ClassificationSchemeDialog(QgsDialog): d.exec_() if d.result() == QDialog.Accepted: - return d.classificationSheme() + return d.classificationScheme() else: return None @@ -258,7 +488,7 @@ class ClassificationSchemeDialog(QgsDialog): s = "" def classificationScheme(self): - return self.w.crosshairStyle() + return self.w.classificationScheme() def setClassificationScheme(self, classificationScheme): assert isinstance(classificationScheme, ClassificationScheme) @@ -275,6 +505,7 @@ if __name__ == '__main__': pathClassImg = r'D:\Repositories\QGIS_Plugins\enmap-box\enmapbox\testdata\HymapBerlinA\HymapBerlinA_test.img' pathShp = r'' + w = ClassificationSchemeWidget() w.setClassificationScheme(ClassificationScheme.fromRasterImage(pathClassImg)) w.show() -- GitLab