Newer
Older
# -*- coding: utf-8 -*-
# noinspection PyPep8Naming
"""
***************************************************************************
spectrallibraries.py
Spectral Profiles and Libraries for a GUI context.
---------------------
Date : Juli 2017
Copyright : (C) 2017 by Benjamin Jakimow
Email : benjamin.jakimow@geo.hu-berlin.de
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
#see http://python-future.org/str_literals.html for str issue discussion

Benjamin Jakimow
committed
import os, re, tempfile, pickle, copy, shutil, locale, uuid, csv, io
from collections import OrderedDict
from qgis.core import *
from qgis.gui import *

Benjamin Jakimow
committed
from qgis.utils import qgsfunction
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *

Benjamin Jakimow
committed
from qgis.core import QgsField, QgsFields, QgsFeature, QgsMapLayer, QgsVectorLayer, QgsConditionalStyle
from qgis.gui import QgsMapCanvas, QgsDockWidget
from pyqtgraph.widgets.PlotWidget import PlotWidget
from pyqtgraph.graphicsItems.PlotDataItem import PlotDataItem

Benjamin Jakimow
committed
from pyqtgraph.graphicsItems.PlotItem import PlotItem

Benjamin Jakimow
committed
import pyqtgraph.functions as fn
import numpy as np
from timeseriesviewer.virtualrasters import describeRawFile

Benjamin Jakimow
committed
from timeseriesviewer.plotstyling import PlotStyle, PlotStyleDialog, MARKERSYMBOLS2QGIS_SYMBOLS

Benjamin Jakimow
committed
FILTERS = 'ENVI Spectral Library + CSV (*.esl *.sli);;CSV Table (*.csv);;ESRI Shapefile (*.shp)'
PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL
HIDDEN_ATTRIBUTE_PREFIX = '__serialized__'
CURRENT_SPECTRUM_STYLE = PlotStyle()
CURRENT_SPECTRUM_STYLE.linePen.setStyle(Qt.SolidLine)
CURRENT_SPECTRUM_STYLE.linePen.setColor(Qt.green)

Benjamin Jakimow
committed
DEFAULT_SPECTRUM_STYLE = PlotStyle()
DEFAULT_SPECTRUM_STYLE.linePen.setStyle(Qt.SolidLine)
DEFAULT_SPECTRUM_STYLE.linePen.setColor(Qt.white)

Benjamin Jakimow
committed
EMPTY_VALUES = [None, NULL, QVariant()]

Benjamin Jakimow
committed
#CURRENT_SPECTRUM_STYLE.linePen
#pdi.setPen(fn.mkPen(QColor('green'), width=3))
def gdalDataset(pathOrDataset, eAccess=gdal.GA_ReadOnly):
"""
:param pathOrDataset: path or gdal.Dataset
:return: gdal.Dataset
"""
if isinstance(pathOrDataset, QgsRasterLayer):
return gdalDataset(pathOrDataset.source())
if not isinstance(pathOrDataset, gdal.Dataset):
pathOrDataset = gdal.Open(pathOrDataset, eAccess)
assert isinstance(pathOrDataset, gdal.Dataset)
return pathOrDataset

Benjamin Jakimow
committed
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def findTypeFromString(value:str):
"""
Returns a fitting basic data type of a string value
:param value: string
:return: type
"""
for t in (int, float):
try:
_ = t(value)
except ValueError:
continue
return t
#every values can be converted into a string
return str
def toType(t, arg, empty2None=True):
"""
Converts lists or single values into type t.
Examples:
toType(int, '42') == 42,
toType(float, ['23.42', '123.4']) == [23.42, 123.4]
:param t: type
:param arg: value to convert
:param empty2None: returns None in case arg is an emptry value (None, '', NoneType, ...)
:return: arg as type t (or None)
"""
if isinstance(arg, list):
return [toType(t, a) for a in arg]
else:
if empty2None and arg in EMPTY_VALUES:
return None
else:
return t(arg)

Benjamin Jakimow
committed
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
@qgsfunction(0, "Spectral Libraries")
def plotStyleSymbolFillColor(values, feature, parent):
if isinstance(feature, QgsFeature):
i = feature.fieldNameIndex(HIDDEN_ATTRIBUTE_PREFIX+'style')
if i >= 0:
style = pickle.loads(feature.attribute(i))
if isinstance(style, PlotStyle):
r,g,b,a = style.markerBrush.color().getRgb()
return '{},{},{},{}'.format(r,g,b, a)
return None
@qgsfunction(0, "Spectral Libraries")
def plotStyleSymbol(values, feature, parent):
if isinstance(feature, QgsFeature):
i = feature.fieldNameIndex(HIDDEN_ATTRIBUTE_PREFIX+'style')
if i >= 0:
style = pickle.loads(feature.attribute(i))
if isinstance(style, PlotStyle):
symbol = style.markerSymbol
qgisSymbolString = MARKERSYMBOLS2QGIS_SYMBOLS.get(symbol)
if isinstance(qgisSymbolString, str):
return qgisSymbolString
return None
@qgsfunction(0, "Spectral Libraries")
def plotStyleSymbolSize(values, feature, parent):
if isinstance(feature, QgsFeature):
i = feature.fieldNameIndex(HIDDEN_ATTRIBUTE_PREFIX+'style')
if i >= 0:
style = pickle.loads(feature.attribute(i))
if isinstance(style, PlotStyle):
return style.markerSize
return None
QgsExpression.registerFunction(plotStyleSymbolFillColor)
QgsExpression.registerFunction(plotStyleSymbol)
QgsExpression.registerFunction(plotStyleSymbolSize)
#Lookup table for ENVI IDL DataTypes to GDAL Data Types
LUT_IDL2GDAL = {1:gdal.GDT_Byte,
12:gdal.GDT_UInt16,
2:gdal.GDT_Int16,
13:gdal.GDT_UInt32,
3:gdal.GDT_Int32,
4:gdal.GDT_Float32,
5:gdal.GDT_Float64,
#:gdal.GDT_CInt16,
#8:gdal.GDT_CInt32,
6:gdal.GDT_CFloat32,
9:gdal.GDT_CFloat64}

Benjamin Jakimow
committed
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def createStandardFields():
fields = QgsFields()
"""
Parameters
name Field name type Field variant type, currently supported: String / Int / Double
typeName Field type (e.g., char, varchar, text, int, serial, double). Field types are usually unique to the source and are stored exactly as returned from the data store.
len Field length
prec Field precision. Usually decimal places but may also be used in conjunction with other fields types (e.g., variable character fields)
comment Comment for the field
subType If the field is a collection, its element's type. When all the elements don't need to have the same type, leave this to QVariant::Invalid.
"""
fields.append(createQgsField('name', ''))
fields.append(createQgsField('px_x', 0))
fields.append(createQgsField('px_y', 0))
fields.append(createQgsField('x_unit', ''))
fields.append(createQgsField('y_unit', ''))
fields.append(createQgsField('source', ''))
fields.append(createQgsField(HIDDEN_ATTRIBUTE_PREFIX + 'xvalues', ''))
fields.append(createQgsField(HIDDEN_ATTRIBUTE_PREFIX + 'yvalues', ''))
fields.append(createQgsField(HIDDEN_ATTRIBUTE_PREFIX + 'style', ''))
"""
fields.append(QgsField('name', QVariant.String,'varchar', 25))
fields.append(QgsField('px_x', QVariant.Int, 'int'))
fields.append(QgsField('px_y', QVariant.Int, 'int'))
fields.append(QgsField('x_unit', QVariant.String, 'varchar', 5))
fields.append(QgsField('y_unit', QVariant.String, 'varchar', 5))
fields.append(QgsField('source', QVariant.String, 'varchar', 5))
"""
return fields

Benjamin Jakimow
committed
def value2str(value, sep=' '):
if isinstance(value, list):

Benjamin Jakimow
committed
value = sep.join([value2str(v, sep=sep) for v in value])
elif isinstance(value, np.ndarray):
value = value2str(value.astype(list), sep=sep)

Benjamin Jakimow
committed
elif value in EMPTY_VALUES:
value = ''
return value

Benjamin Jakimow
committed
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
class AddAttributeDialog(QDialog):
def __init__(self, layer, parent=None):
assert isinstance(layer, QgsVectorLayer)
super(AddAttributeDialog, self).__init__(parent)
assert isinstance(layer, QgsVectorLayer)
self.mLayer = layer
self.setWindowTitle('Add Field')
l = QGridLayout()
self.tbName = QLineEdit('Name')
self.tbName.setPlaceholderText('Name')
self.tbName.textChanged.connect(self.validate)
l.addWidget(QLabel('Name'), 0,0)
l.addWidget(self.tbName, 0, 1)
self.tbComment = QLineEdit()
self.tbComment.setPlaceholderText('Comment')
l.addWidget(QLabel('Comment'), 1, 0)
l.addWidget(self.tbComment, 1, 1)
self.cbType = QComboBox()
self.typeModel = OptionListModel()
for ntype in self.mLayer.dataProvider().nativeTypes():
assert isinstance(ntype, QgsVectorDataProvider.NativeType)
o = Option(ntype,name=ntype.mTypeName, tooltip=ntype.mTypeDesc)
self.typeModel.addOption(o)
self.cbType.setModel(self.typeModel)
self.cbType.currentIndexChanged.connect(self.onTypeChanged)
l.addWidget(QLabel('Type'), 2, 0)
l.addWidget(self.cbType, 2, 1)
self.sbLength = QSpinBox()
self.sbLength.setRange(0, 99)
self.sbLength.valueChanged.connect(lambda : self.setPrecisionMinMax())
self.lengthLabel = QLabel('Length')
l.addWidget(self.lengthLabel, 3, 0)
l.addWidget(self.sbLength, 3, 1)
self.sbPrecision = QSpinBox()
self.sbPrecision.setRange(0, 99)
self.precisionLabel = QLabel('Precision')
l.addWidget(self.precisionLabel, 4, 0)
l.addWidget(self.sbPrecision, 4, 1)
self.tbValidationInfo = QLabel()
self.tbValidationInfo.setStyleSheet("QLabel { color : red}")
l.addWidget(self.tbValidationInfo, 5, 0, 1, 2)
self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttons.button(QDialogButtonBox.Ok).clicked.connect(self.accept)
self.buttons.button(QDialogButtonBox.Cancel).clicked.connect(self.reject)
l.addWidget(self.buttons, 6, 1)
self.setLayout(l)
self.mLayer = layer
self.onTypeChanged()
def accept(self):
msg = self.validate()
if len(msg) > 0:
QMessageBox.warning(self, "Add Field", msg)
else:
super(AddAttributeDialog, self).accept()
def field(self):
"""
Returns the new QgsField
:return:
"""
ntype = self.currentNativeType()
return QgsField(name=self.tbName.text(),
type=QVariant(ntype.mType).type(),
typeName=ntype.mTypeName,
len=self.sbLength.value(),
prec=self.sbPrecision.value(),
comment=self.tbComment.text())
def currentNativeType(self):
return self.cbType.currentData().value()
def onTypeChanged(self, *args):
ntype = self.currentNativeType()
vMin , vMax = ntype.mMinLen, ntype.mMaxLen
assert isinstance(ntype, QgsVectorDataProvider.NativeType)
isVisible = vMin < vMax
self.sbLength.setVisible(isVisible)
self.lengthLabel.setVisible(isVisible)
self.setSpinBoxMinMax(self.sbLength, vMin , vMax)
self.setPrecisionMinMax()
def setPrecisionMinMax(self):
ntype = self.currentNativeType()
vMin, vMax = ntype.mMinPrec, ntype.mMaxPrec
isVisible = vMin < vMax
self.sbPrecision.setVisible(isVisible)
self.precisionLabel.setVisible(isVisible)
vMax = max(ntype.mMinPrec, min(ntype.mMaxPrec, self.sbLength.value()))
self.setSpinBoxMinMax(self.sbPrecision, vMin, vMax)
def setSpinBoxMinMax(self, sb, vMin, vMax):
assert isinstance(sb, QSpinBox)
value = sb.value()
sb.setRange(vMin, vMax)
if value > vMax:
sb.setValue(vMax)
elif value < vMin:
sb.setValue(vMin)
def validate(self):
msg = []
name = self.tbName.text()
if name in self.mLayer.fields().names():
msg.append('Field name "{}" already exists.'.format(name))
elif name == '':
msg.append('Missing field name')
elif name == 'shape':
msg.append('Field name "{}" already reserved.'.format(name))
msg = '\n'.join(msg)
self.buttons.button(QDialogButtonBox.Ok).setEnabled(len(msg) == 0)
self.tbValidationInfo.setText(msg)
return msg
class SpectralLibraryTableFilterModel(QgsAttributeTableFilterModel):
def __init__(self, sourceModel, parent=None):
dummyCanvas = QgsMapCanvas()
dummyCanvas.setDestinationCrs(SpectralProfile.crs)
dummyCanvas.setExtent(QgsRectangle(-180,-90,180,90))
super(SpectralLibraryTableFilterModel, self).__init__(dummyCanvas, sourceModel, parent=parent)
self.mDummyCanvas = dummyCanvas
#self.setSelectedOnTop(True)
class SpectralLibraryTableView(QgsAttributeTableView):
def __init__(self, parent=None):
super(SpectralLibraryTableView, self).__init__(parent)

Benjamin Jakimow
committed
#self.setSelectionBehavior(QAbstractItemView.SelectRows)
#self.setSelectionMode(QAbstractItemView.SingleSelection)

Benjamin Jakimow
committed
self.horizontalHeader().setSectionsMovable(True)
self.willShowContextMenu.connect(self.onWillShowContextMenu)
self.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)

Benjamin Jakimow
committed
super(SpectralLibraryTableView, self).setModel(filterModel)
self.mSelectionManager = SpectralLibraryFeatureSelectionManager(self.model().layer())
self.setFeatureSelectionManager(self.mSelectionManager)

Benjamin Jakimow
committed
#self.selectionModel().selectionChanged.connect(self.onSelectionChanged)
#def contextMenuEvent(self, event):
def onWillShowContextMenu(self, menu, index):
assert isinstance(menu, QMenu)
assert isinstance(index, QModelIndex)

Benjamin Jakimow
committed
featureIDs = self.spectralLibrary().selectedFeatureIds()
if len(featureIDs) == 0 and index.isValid():
if isinstance(self.model(), QgsAttributeTableFilterModel):
index = self.model().mapToSource(index)
if index.isValid():
featureIDs.append(self.model().sourceModel().feature(index).id())
elif isinstance(self.model(), QgsAttributeTableFilterModel):
featureIDs.append(self.model().feature(index).id())

Benjamin Jakimow
committed

Benjamin Jakimow
committed
m = menu.addMenu('Copy...')
a = m.addAction("Values")
a.triggered.connect(lambda b, ids=featureIDs, mode=ClipboardIO.WritingModes.VALUES: self.onCopy2Clipboard(ids, mode))
a = m.addAction("Attributes")
a.triggered.connect(lambda b, ids=featureIDs, mode=ClipboardIO.WritingModes.ATTRIBUTES: self.onCopy2Clipboard(ids, mode))
a = m.addAction("Values + Attributes")
a.triggered.connect(lambda b, ids=featureIDs, mode=ClipboardIO.WritingModes.ALL: self.onCopy2Clipboard(ids, mode))

Benjamin Jakimow
committed
a = menu.addAction('Save as...')
a.triggered.connect(lambda b, ids=featureIDs : self.onSaveToFile(ids))
menu.addSeparator()

Benjamin Jakimow
committed
a = menu.addAction('Set Style')
a.triggered.connect(lambda b, ids=featureIDs : self.onSetStyle(ids))

Benjamin Jakimow
committed
a = menu.addAction('Check')
a.triggered.connect(lambda : self.setCheckState(featureIDs, Qt.Checked))
a = menu.addAction('Uncheck')
a.triggered.connect(lambda: self.setCheckState(featureIDs, Qt.Unchecked))
menu.addSeparator()
for a in self.actions():
menu.addAction(a)
def spectralLibrary(self):
return self.model().layer()

Benjamin Jakimow
committed
assert isinstance(fids, list)
speclib = self.spectralLibrary()
assert isinstance(speclib, SpectralLibrary)
speclib = speclib.speclibFromFeatureIDs(fids)
ClipboardIO.write(speclib, mode=mode)
s = ""
def onSaveToFile(self, fids):
speclib = self.spectralLibrary()
assert isinstance(speclib, SpectralLibrary)
speclib.getFeatures(fids)
speclib.exportProfiles()

Benjamin Jakimow
committed
def fidsToIndices(self, fids):

Benjamin Jakimow
committed
Converts feature ids into FilterModel QModelIndices
:param fids: [list-of-int]
:return:

Benjamin Jakimow
committed
if isinstance(fids, int):
fids = [fids]
assert isinstance(fids, list)

Benjamin Jakimow
committed
indices = [fmodel.fidToIndex(id) for id in fids]

Benjamin Jakimow
committed

Benjamin Jakimow
committed
def onRemoveFIDs(self, fids):
speclib = self.spectralLibrary()
assert isinstance(speclib, SpectralLibrary)

Benjamin Jakimow
committed
b = speclib.isEditable()

Benjamin Jakimow
committed
speclib.startEditing()

Benjamin Jakimow
committed
speclib.deleteFeatures(fids)

Benjamin Jakimow
committed
saveEdits(speclib, leaveEditable=b)

Benjamin Jakimow
committed
def onSetStyle(self, ids):
if len(ids) == 0:
return
speclib = self.spectralLibrary()
assert isinstance(speclib, SpectralLibrary)
profiles = speclib.profiles(ids)
refProfile = profiles[0]
styleDefault = refProfile.style()
refStyle = PlotStyleDialog.getPlotStyle(plotStyle=styleDefault)

Benjamin Jakimow
committed
if isinstance(refStyle, PlotStyle):
refProfile.setStyle(refStyle)

Benjamin Jakimow
committed
iStyle = speclib.fields().indexFromName(HIDDEN_ATTRIBUTE_PREFIX+'style')
assert iStyle >= 0
if isinstance(refStyle, PlotStyle):

Benjamin Jakimow
committed
b = speclib.isEditable()

Benjamin Jakimow
committed
speclib.startEditing()
for f in profiles:
assert isinstance(f, SpectralProfile)
oldStyle = f.style()
refStyle.setVisibility(oldStyle.isVisible())
speclib.changeAttributeValue(f.id(), iStyle, pickle.dumps(refStyle), f.attributes()[iStyle])

Benjamin Jakimow
committed
saveEdits(speclib, leaveEditable=b)

Benjamin Jakimow
committed

Benjamin Jakimow
committed

Benjamin Jakimow
committed
def setCheckState(self, fids, checkState):

Benjamin Jakimow
committed
speclib = self.spectralLibrary()
speclib.startEditing()

Benjamin Jakimow
committed
profiles = speclib.profiles(fids)

Benjamin Jakimow
committed
iStyle = speclib.fields().indexFromName(HIDDEN_ATTRIBUTE_PREFIX + 'style')
setVisible = checkState == Qt.Checked

Benjamin Jakimow
committed
b = speclib.isEditable()
speclib.startEditing()

Benjamin Jakimow
committed
for p in profiles:
assert isinstance(p, SpectralProfile)
oldStyle = p.style()
assert isinstance(oldStyle, PlotStyle)
if oldStyle.isVisible() != setVisible:
newStyle = p.style()
newStyle.setVisibility(setVisible)
p.setStyle(newStyle)
speclib.changeAttributeValue(p.id(), iStyle, p.attributes()[iStyle], oldStyle)

Benjamin Jakimow
committed
saveEdits(speclib, leaveEditable=b)

Benjamin Jakimow
committed
def dropEvent(self, event):
assert isinstance(event, QDropEvent)
mimeData = event.mimeData()
if self.model().rowCount() == 0:
index = self.model().createIndex(0,0)
else:
index = self.indexAt(event.pos())
if mimeData.hasFormat(mimedata.MDF_SPECTRALLIBRARY):
self.model().dropMimeData(mimeData, event.dropAction(), index.row(), index.column(), index.parent())
event.accept()
def dragEnterEvent(self, event):
assert isinstance(event, QDragEnterEvent)
if event.mimeData().hasFormat(mimedata.MDF_SPECTRALLIBRARY):
event.accept()
def dragMoveEvent(self, event):
assert isinstance(event, QDragMoveEvent)
if event.mimeData().hasFormat(mimedata.MDF_SPECTRALLIBRARY):
event.accept()
s = ""
def mimeTypes(self):
pass
"""
class SpectralProfileMapTool(QgsMapToolEmitPoint):
sigProfileRequest = pyqtSignal(SpatialPoint, QgsMapCanvas)
def __init__(self, canvas, showCrosshair=True):
self.mShowCrosshair = showCrosshair
self.mCanvas = canvas
QgsMapToolEmitPoint.__init__(self, self.mCanvas)
self.marker = QgsVertexMarker(self.mCanvas)
self.rubberband = QgsRubberBand(self.mCanvas, QgsWkbTypes.PolygonGeometry)
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
color = QColor('red')
self.rubberband.setLineStyle(Qt.SolidLine)
self.rubberband.setColor(color)
self.rubberband.setWidth(2)
self.marker.setColor(color)
self.marker.setPenWidth(3)
self.marker.setIconSize(5)
self.marker.setIconType(QgsVertexMarker.ICON_CROSS) # or ICON_CROSS, ICON_X
def canvasPressEvent(self, e):
geoPoint = self.toMapCoordinates(e.pos())
self.marker.setCenter(geoPoint)
#self.marker.show()
def setStyle(self, color=None, brushStyle=None, fillColor=None, lineStyle=None):
if color:
self.rubberband.setColor(color)
if brushStyle:
self.rubberband.setBrushStyle(brushStyle)
if fillColor:
self.rubberband.setFillColor(fillColor)
if lineStyle:
self.rubberband.setLineStyle(lineStyle)
def canvasReleaseEvent(self, e):
pixelPoint = e.pixelPoint()
crs = self.mCanvas.mapSettings().destinationCrs()
self.marker.hide()
geoPoint = self.toMapCoordinates(pixelPoint)
if self.mShowCrosshair:
#show a temporary crosshair
ext = SpatialExtent.fromMapCanvas(self.mCanvas)
cen = geoPoint
geom = QgsGeometry()
geom.addPart([QgsPointXY(ext.upperLeftPt().x(),cen.y()), QgsPointXY(ext.lowerRightPt().x(), cen.y())],
geom.addPart([QgsPointXY(cen.x(), ext.upperLeftPt().y()), QgsPointXY(cen.x(), ext.lowerRightPt().y())],
self.rubberband.addGeometry(geom, None)
self.rubberband.show()
#remove crosshair after 0.1 sec
QTimer.singleShot(100, self.hideRubberband)
self.sigProfileRequest.emit(SpatialPoint(crs, geoPoint), self.mCanvas)
def hideRubberband(self):
self.rubberband.reset()
"""

Benjamin Jakimow
committed
class SpectralProfilePlotDataItem(PlotDataItem):
def __init__(self, spectralProfile):
assert isinstance(spectralProfile, SpectralProfile)
super(SpectralProfilePlotDataItem, self).__init__()
self.mProfile = spectralProfile

Benjamin Jakimow
committed
self.setData(x=spectralProfile.xValues(), y=spectralProfile.yValues())
self.setStyle(self.mProfile.style())
def setClickable(self, b, width=None):
assert isinstance(b, bool)
self.curve.setClickable(b, width=width)

Benjamin Jakimow
committed
def setStyle(self, style):
assert isinstance(style, PlotStyle)
self.setVisible(style.isVisible())
self.setSymbol(style.markerSymbol)
self.setSymbolBrush(style.markerBrush)
self.setSymbolSize(style.markerSize)
self.setSymbolPen(style.markerPen)
self.setPen(style.linePen)
def setColor(self, color):
if not isinstance(color, QColor):
color = QColor(color)
self.setPen(color)
def pen(self):
return fn.mkPen(self.opts['pen'])
def color(self):
return self.pen().color()
def setLineWidth(self, width):
from pyqtgraph.functions import mkPen
pen = mkPen(self.opts['pen'])
assert isinstance(pen, QPen)
pen.setWidth(width)
self.setPen(pen)
crs = QgsCoordinateReferenceSystem('EPSG:4326')
@staticmethod
def fromMapCanvas(mapCanvas, position):
"""
Returns a list of Spectral Profiles the raster layers in QgsMapCanvas mapCanvas.
:param mapCanvas:
:param position:
"""
assert isinstance(mapCanvas, QgsMapCanvas)
from timeseriesviewer.mapcanvas import MapCanvas
if isinstance(mapCanvas, MapCanvas):
sources = mapCanvas.layerModel().rasterLayerInfos()
sources = [s.mSrc for s in sources]
else:
layers = [l for l in mapCanvas.layers() if isinstance(l, QgsRasterLayer)]
sources = [l.source() for l in layers]
return SpectralProfile.fromRasterSources(sources, position)
@staticmethod
def fromRasterSources(sources, position):
"""
Returns a list of Spectral Profiles
:param sources: list-of-raster-sources, e.g. file paths, gdal.Datasets, QgsRasterLayers
:param position:
:return:
"""
profiles = [SpectralProfile.fromRasterSource(s, position) for s in sources]
return [p for p in profiles if isinstance(p, SpectralProfile)]
@staticmethod
def fromRasterSource(source, position):
"""
Returns the Spectral Profiles from source at position `position`
:param source: path or gdal.Dataset
:param position:
:return: SpectralProfile
"""
ds = gdalDataset(source)
files = ds.GetFileList()
if len(files) > 0:
baseName = os.path.basename(files[0])
else:
baseName = 'Spectrum'
crs = QgsCoordinateReferenceSystem(ds.GetProjection())
gt = ds.GetGeoTransform()
if isinstance(position, QPoint):
px = position
elif isinstance(position, SpatialPoint):
px = geo2px(position.toCrs(crs), gt)
elif isinstance(position, QgsPointXY):
px = geo2px(position, ds.GetGeoTransform())
else:
raise Exception('Unsupported type of argument "position" {}'.format('{}'.format(position)))
#check out-of-raster
if px.x() < 0 or px.y() < 0: return None
if px.x() > ds.RasterXSize - 1 or px.y() > ds.RasterYSize - 1: return None
values = ds.ReadAsArray(px.x(), px.y(), 1, 1)
values = values.flatten()
for b in range(ds.RasterCount):
band = ds.GetRasterBand(b+1)
nodata = band.GetNoDataValue()
if nodata and values[b] == nodata:
return None
wl = ds.GetMetadataItem(str('wavelength'), str('ENVI'))
wlu = ds.GetMetadataItem(str('wavelength_units'), str('ENVI'))
if wl is not None and len(wl) > 0:
wl = re.sub(r'[ {}]','', wl).split(',')
wl = [float(w) for w in wl]
profile = SpectralProfile()
profile.setName('{} x{} y{}'.format(baseName, px.x(), px.y()))
#profile.setValues(values, valuePositions=wl, valuePositionUnit=wlu)
profile.setYValues(values)
if wl is not None:
profile.setXValues(wl, unit=wlu)
profile.setCoordinates(px=px, pt=SpatialPoint(crs, px2geo(px, gt)))
profile.setSource('{}'.format(ds.GetFileList()[0]))
return profile
@staticmethod
def fromSpecLibFeature(feature):
sp = SpectralProfile(fields=feature.fields())
sp.setAttributes(feature.attributes())
return sp
XVALUES_FIELD = HIDDEN_ATTRIBUTE_PREFIX+'xvalues'
YVALUES_FIELD = HIDDEN_ATTRIBUTE_PREFIX + 'yvalues'
STYLE_FIELD = HIDDEN_ATTRIBUTE_PREFIX + 'style'
def __init__(self, parent=None, fields=None, xUnit='index', yUnit=None):

Benjamin Jakimow
committed
fields = createStandardFields()
#QgsFeature.__init__(self, fields)
#QObject.__init__(self)
#QObject.__init__(self)
fields = self.fields()
assert isinstance(fields, QgsFields)
self.setXUnit(xUnit)
self.setYUnit(yUnit)

Benjamin Jakimow
committed
self.setStyle(DEFAULT_SPECTRUM_STYLE)
def fieldNames(self):
return self.fields().names()
def setName(self, name:str):
if name != self.name():
self.setAttribute('name', name)
#self.sigNameChanged.emit(name)
def name(self):
return self.metadata('name')
def setSource(self, uri: str):
self.setAttribute('source', uri)
def source(self):
return self.metadata('source')
if isinstance(px, QPoint):
self.setAttribute('px_x', px.x())
self.setAttribute('px_y', px.y())
if isinstance(pt, SpatialPoint):
sp = pt.toCrs(SpectralProfile.crs)
self.setGeometry(QgsGeometry.fromPointXY(sp))
def pxCoordinate(self):
x = self.attribute('px_x')
y = self.attribute('px_y')
if x == None or y == None:
return None
return QPoint(x, y)
def geoCoordinate(self):
def isValid(self):
return len(self.mValues) > 0 and self.mValueUnit is not None
def setXValues(self, values, unit=None):
if isinstance(values, np.ndarray):
values = values.tolist()
assert isinstance(values, list)
self.setMetadata(SpectralProfile.XVALUES_FIELD, values)
if isinstance(unit, str):
self.setMetadata('x_unit', unit)
def setYValues(self, values, unit=None):
if isinstance(values, np.ndarray):
values = values.tolist()
assert isinstance(values, list)
self.setMetadata(SpectralProfile.YVALUES_FIELD, values)
if isinstance(unit, str):
self.setMetadata('y_unit', unit)
if self.xValues() is None:
self.setXValues(list(range(len(values))), unit='index')
def style(self):
return self.metadata(SpectralProfile.STYLE_FIELD)
def setStyle(self, style):
assert isinstance(style, PlotStyle)
self.setMetadata(SpectralProfile.STYLE_FIELD, style)
def updateMetadata(self, metaData):
if isinstance(metaData, dict):
for key, value in metaData.items():
self.setMetadata(key, value)
def removeField(self, name):
fields = self.fields()
values = self.attributes()
i = self.fieldNameIndex(name)
if i >= 0:
fields.remove(i)
values.pop(i)
self.setFields(fields)
self.setAttributes(values)
def setMetadata(self, key: str, value, addMissingFields=False):
"""
:param key: Name of metadata field
:param value: value to add. Need to be of type None, str, int or float.
:param addMissingFields: Set on True to add missing fields (in case value is not None)
:return:
"""
i = self.fieldNameIndex(key)
if key.startswith('__serialized__'):
if value is not None:
value = pickle.dumps(value)
if value is not None and addMissingFields:
fields = self.fields()
values = self.attributes()
if key.startswith('__serialized__'):
else:
values.append(value)
self.setFields(fields)
self.setAttributes(values)
return False
else:
return self.setAttribute(key, value)
def metadata(self, key: str, default=None):
i = self.fieldNameIndex(key)
if i < 0:
return None
v = self.attribute(i)
if v == QVariant(None):
v = None
if key.startswith('__serialized__') and v != None:
v = pickle.loads(v)
return default if v is None else v
def xValues(self):
return self.metadata(SpectralProfile.XVALUES_FIELD)
return self.metadata(SpectralProfile.YVALUES_FIELD)
def setXUnit(self, unit : str='index'):
self.setMetadata('x_unit', unit)
def xUnit(self):
def setYUnit(self, unit:str=None):
self.setMetadata('y_unit', unit)
def yUnit(self):
return self.metadata('y_unit', None)

Benjamin Jakimow
committed
def copyFieldSubset(self, fields):
sp = SpectralProfile(fields=fields)
fieldsInCommon = [field for field in sp.fields() if field in self.fields()]
sp.setGeometry(self.geometry())
sp.setId(self.id())
for field in fieldsInCommon:
assert isinstance(field, QgsField)
i = sp.fieldNameIndex(field.name())
sp.setAttribute(i, self.attribute(field.name()))
return sp
def clone(self):
sp = SpectralProfile(fields=self.fields())
sp.setAttributes(self.attributes())
return sp
def plot(self):
"""
Plots this profile to an new PyQtGraph window
:return:
"""
import pyqtgraph as pg
pi = SpectralProfilePlotDataItem(self)
pi.setClickable(True)
pw = pg.plot( title=self.name())