Commit 326e1c36 authored by Benjamin Jakimow's avatar Benjamin Jakimow
Browse files

refactored labeling

added labeling docs
parent ed6bf9ea
......@@ -44,7 +44,9 @@ Features
License and Use
---------------
This program is free software; you can redistribute it and/or modify it under the terms of the `GNU General Public License Version 3 (GNU GPL-3) <https://www.gnu.org/licenses/gpl-3.0.en.html>`_ , as published by the Free Software Foundation. See also :ref:`License`.
This program is free software; you can redistribute it and/or modify it under the terms of the
`GNU General Public License Version 3 (GNU GPL-3) <https://www.gnu.org/licenses/gpl-3.0.en.html>`_ ,
as published by the Free Software Foundation. See also :ref:`License`.
.. note that changelog.rst is autogenerated!!!
......
......@@ -40,7 +40,7 @@ Quick Start
.. note:: Use |mActionRefresh| to refresh or redraw related maps, spectral profile plots etc.
6. Now we like to optimize the color stretch. Choose a none-clouded Landsat observation like 2014-06-24 and use the map context menu (right-mouse-click)
6. Now we like to optimize the color stretch. Choose a -clouded Landsat observation like 2014-06-24 and use the map context menu (right-mouse-click)
to click on :menuselection:`Stretch to current extent ... --> Linear 5%`. Repeat with `Linear` and `Gaussian` stretch as well as for RapidEye images to
see how this changes the band-specific min/max values in the Map View settings.
......@@ -161,7 +161,7 @@ The Toolbar
.. note::
Only after |select_location| :sup:`Identify Pixels and Features` is activated you can select the other identify tools
(|mActionPropertiesWidget|, |profile|, |mIconTemporalProfile|). You can activate them all at once as well as none of them,
(|mActionPropertiesWidget|, |profile|, |mIconTemporalProfile|). You can activate them all at once as well as of them,
in case of the latter variant clicking in the map has no direct effect (other than moving the crosshair, when activated)
......@@ -340,22 +340,37 @@ The spectral library view allows you to visualize, label and export spectral pro
Quick Labeling
--------------
The EO Time Series Viewer assists you in describing, i.e. *label* reference data based on the time series visualize in the EO Time Series Viewer,
Basically, there are possible labeling scenarios:
a) You need to identify spatial objects of interest and annotate them with one or multiple attributes. For example, you use the EO Time series viewer
to identify exemplary land cover types, digitize them as polygons and annotate these polygons with a land cover type ("forest") and the year of observation ("2014")
b) The spatial objects of interest already exists, but you need to annotate them, for example to annotate a set of random sampled points whether they got deforested or not,
e.g. for an map accuracy assessment.
In both cases, you like to fill the attribute table of a vector layer based on temporal and contextual information.
The EO Time Series Viewer supports this work by "Quick Label" short-cuts which automatically fill the attributes values
for features selected in the vector layer.
The EO Time Series Viewer assists you in describing, i.e. *label*, reference data, e.g. to describe at the occurence of
land cover types and events. Whether your
locations (point, lines or polygons) of interest already exist, or are being digitized in the visualized maps first,
in both cases you want to describe them in the attribute table of the vector layer.
The EO Time Series Viewer supports this with "Quick Label" short-cuts in the map context menu. They send temporal or
categorical information direct to the attribute cells of selected geometries.
Timestamp: 2019-02-05T11:23:42.00
Sensor Name: 'LND' (Landsat)
+-------------------+---------------------+------+----------+---------------------+------------+----------+------+------+
| | Type of linked vector layer field |
+-------------------+---------------------+------+----------+---------------------+------------+----------+------+------+
| LabelType | varchar | int | double | datetime | date | time | bool | blob |
+===================+=====================+======+==========+=====================+============+==========+======+======+
| Date | 2019-02-05 | | | 2019-02-05T00:00:00 | 2019-02-05 | | | |
+-------------------+---------------------+------+----------+---------------------+------------+----------+------+------+
| Date-Time | 2019-02-05T11:23:42 | | | 2019-02-05T11:23:42 | 2019-02-05 | | | |
+-------------------+---------------------+------+----------+---------------------+------------+----------+------+------+
| Time | 11:23:42 | | | 2019-02-05T11:23:42 | | 11:23:42 | | |
+-------------------+---------------------+------+----------+---------------------+------------+----------+------+------+
| Day of Year (DOY) | 36 | 36 | 36 | | | | | |
+-------------------+---------------------+------+----------+---------------------+------------+----------+------+------+
| Year | 2019 | 2019 | | 2019-02-05T11:23:42 | 2019-02-05 | 11:23:42 | | |
+-------------------+---------------------+------+----------+---------------------+------------+----------+------+------+
| Decimal Year | 2019.0980926430518 | 2019 | 2019.098 | | | | | |
+-------------------+---------------------+------+----------+---------------------+------------+----------+------+------+
| Sensor Name | LND | | | | | | | |
+-------------------+---------------------+------+----------+---------------------+------------+----------+------+------+
| Source Image | /path/to/image | | | | | | | |
+-------------------+---------------------+------+----------+---------------------+------------+----------+------+------+
\ No newline at end of file
......@@ -10,7 +10,6 @@ from qgis.gui import QgsDockWidget, QgsSpinBox, QgsDoubleSpinBox, \
QgsGui, QgsEditorWidgetRegistry, \
QgsDateTimeEdit, QgsDateEdit, QgsTimeEdit
from eotimeseriesviewer.externals.qps.layerproperties import showLayerPropertiesDialog, AttributeTableWidget
from eotimeseriesviewer.timeseries import TimeSeriesDate, TimeSeriesSource
from eotimeseriesviewer import DIR_UI
......@@ -22,17 +21,19 @@ from .externals.qps.classification.classificationscheme import ClassInfo, Classi
from .externals.qps.layerproperties import showLayerPropertiesDialog
from .externals.qps.models import Option, OptionListModel
from .externals.qps.classification.classificationscheme import EDITOR_WIDGET_REGISTRY_KEY as CS_KEY
# the QgsProject(s) and QgsMapLayerStore(s) to search for QgsVectorLayers
MAP_LAYER_STORES = [QgsProject.instance()]
EDITOR_WIDGET_REGISTRY_KEY = 'EOTSV Quick Label'
class LabelConfigurationKey(object):
class LabelConfigurationKey(object):
ClassificationScheme = 'classificationScheme'
LabelType = 'labelType'
LabelGroup = 'labelGroup'
class LabelShortcutType(enum.Enum):
"""Enumeration for shortcuts to be derived from a TimeSeriesDate instance"""
Off = 'No Quick Label (default)'
......@@ -44,8 +45,8 @@ class LabelShortcutType(enum.Enum):
DecimalYear = 'Decimal Year'
Sensor = 'Sensor Name'
SourceImage = 'Source Image'
# Classification = 'Classification'
# Classification = 'Classification'
@staticmethod
def fromConfValue(value: str):
......@@ -54,18 +55,16 @@ class LabelShortcutType(enum.Enum):
return t
return LabelShortcutType.Off
def confValue(self) -> str:
return self.name
def text(self) -> str:
return self.value
def __str__(self):
return str(self.name)
def shortcuts(field: QgsField) -> typing.List[LabelShortcutType]:
"""
Returns the possible LabelShortCutTypes for a certain field
......@@ -107,7 +106,7 @@ def shortcuts(field: QgsField) -> typing.List[LabelShortcutType]:
return result
def layerClassSchemes(layer: QgsVectorLayer) -> list:
def layerClassSchemes(layer: QgsVectorLayer) -> typing.List[ClassificationScheme]:
"""
Returns a list of (ClassificationScheme, QgsField) for all QgsFields with QgsEditorWidget being QgsClassificationWidgetWrapper or RasterClassification.
:param layer: QgsVectorLayer
......@@ -168,7 +167,6 @@ def quickLabelLayers() -> typing.List[QgsVectorLayer]:
"""
layers = []
classSchemes = set()
for store in MAP_LAYER_STORES:
assert isinstance(store, (QgsProject, QgsMapLayerStore))
......@@ -286,47 +284,11 @@ def setQuickTSDLabels(vectorLayer: QgsVectorLayer,
conf = setup.config()
labelType: LabelShortcutType = LabelShortcutType.fromConfValue(conf.get(LabelConfigurationKey.LabelType))
value = None
if labelType == LabelShortcutType.Off:
continue
if labelType == LabelShortcutType.Sensor:
value = tsd.sensor().name()
elif labelType == LabelShortcutType.DOY:
value = tsd.doy()
elif labelType in [LabelShortcutType.Date, LabelShortcutType.DateTime]:
if fieldType == QVariant.Date:
value = QDate(tsd.date().astype(object))
elif fieldType == QVariant.DateTime:
value = QDateTime(tsd.date().astype(object))
elif fieldType == QVariant.String:
value = str(tsd.date())
elif labelType == LabelShortcutType.Year:
year = tsd.date().astype(object).year
if fieldType == QVariant.String:
value = str(year)
elif fieldType == QVariant.Date:
value = QDate(year, 1, 1)
elif fieldType == QVariant.DateTime:
value = QDateTime(2000, 1, 1, 0, 0)
elif fieldType == QVariant.Int:
value = year
elif labelType == LabelShortcutType.DecimalYear:
value = tsd.decimalYear()
elif labelType == LabelShortcutType.SourceImage and isinstance(tss, TimeSeriesSource):
value = tss.uri()
value = quickLabelValue(fieldType, labelType, tsd, tss)
if value is not None:
if fieldType == QVariant.String:
value = str(value)
for feature in vectorLayer.selectedFeatures():
assert isinstance(feature, QgsFeature)
oldValue = feature.attribute(field.name())
......@@ -336,6 +298,82 @@ def setQuickTSDLabels(vectorLayer: QgsVectorLayer,
pass
def quickLabelValue(fieldType: QVariant,
labelType: LabelShortcutType,
tsd: TimeSeriesDate,
tss: TimeSeriesSource):
value = None
datetime: QDateTime = QDateTime(tsd.date().astype(object))
if labelType == LabelShortcutType.Off:
return value
if labelType == LabelShortcutType.Sensor:
if fieldType == QVariant.String:
value = tsd.sensor().name()
elif labelType == LabelShortcutType.DOY:
if fieldType in [QVariant.Double, QVariant.Int]:
value = tsd.doy()
elif fieldType == QVariant.String:
value = str(tsd.doy())
elif labelType == LabelShortcutType.Date:
if fieldType == QVariant.Date:
value = datetime.date()
elif fieldType == QVariant.DateTime:
value = QDateTime(datetime.date())
elif fieldType == QVariant.String:
value = datetime.date().toPyDate().isoformat()
elif labelType == LabelShortcutType.DateTime:
if fieldType == QVariant.Date:
value = datetime.date()
elif fieldType == QVariant.DateTime:
value = datetime
elif fieldType == QVariant.String:
value = datetime.toPyDateTime().isoformat()
elif labelType == LabelShortcutType.Time:
if fieldType == QVariant.Date:
value = None
elif fieldType == QVariant.DateTime:
value = datetime
elif fieldType == QVariant.Time:
value = datetime.time()
elif fieldType == QVariant.String:
value = datetime.time().toPyTime().isoformat()
elif labelType == LabelShortcutType.Year:
if fieldType == QVariant.String:
value = str(datetime.date().year())
elif fieldType == QVariant.Date:
value = datetime.date()
elif fieldType == QVariant.DateTime:
value = datetime
elif fieldType == QVariant.Time:
value = datetime.time()
elif fieldType == QVariant.Int:
value = datetime.date().year()
elif labelType == LabelShortcutType.DecimalYear:
if fieldType == QVariant.String:
value = str(tsd.decimalYear())
elif fieldType == QVariant.Int:
value = int(tsd.decimalYear())
elif fieldType == QVariant.Double:
value = tsd.decimalYear()
elif labelType == LabelShortcutType.SourceImage and isinstance(tss, TimeSeriesSource):
if fieldType == QVariant.String:
value = tss.uri()
if value is not None and fieldType == QVariant.String:
value = str(value)
return value
class LabelAttributeTableModel(QAbstractTableModel):
def __init__(self, parent=None, *args):
......@@ -661,18 +699,18 @@ class LabelWidget(AttributeTableWidget):
self.mOptionSelectionAddToSelection.setIcon(QIcon(':/images/themes/default/mIconSelectAdd.svg'))
self.mOptionSelectionAddToSelection.setToolTip('Adds a new feature to an existing selection.')
#self.mOptionSelectionIntersectSelection = m.addAction('Intersect Selection')
#self.mOptionSelectionIntersectSelection.setIcon(QIcon(':/images/themes/default/mIconSelectIntersect.svg'))
# self.mOptionSelectionIntersectSelection = m.addAction('Intersect Selection')
# self.mOptionSelectionIntersectSelection.setIcon(QIcon(':/images/themes/default/mIconSelectIntersect.svg'))
#self.mOptionRemoveFromSelection = m.addAction('Remove from Selection')
#self.mOptionRemoveFromSelection.setIcon(QIcon(':/images/themes/default/mIconSelectRemove.svg'))
# self.mOptionRemoveFromSelection = m.addAction('Remove from Selection')
# self.mOptionRemoveFromSelection.setIcon(QIcon(':/images/themes/default/mIconSelectRemove.svg'))
self.mOptionSelectBehaviour.setMenu(m)
for o in [self.mOptionSelectionSetSelection,
self.mOptionSelectionAddToSelection,
#self.mOptionSelectionIntersectSelection,
#self.mOptionRemoveFromSelection
# self.mOptionSelectionIntersectSelection,
# self.mOptionRemoveFromSelection
]:
o.setCheckable(True)
o.triggered.connect(self.onSelectBehaviourOptionTriggered)
......@@ -762,7 +800,7 @@ class LabelShortcutEditorConfigWidget(QgsEditorConfigWidget):
self.btnAddGroup.setDefaultAction(self.actionAddGroup)
self.actionAddGroup.triggered.connect(self.onAddGroup)
#self.setConfig(vl.editorWidgetSetup(fieldIdx).config())
# self.setConfig(vl.editorWidgetSetup(fieldIdx).config())
self.mLastConfig = {}
def onAddGroup(self):
......@@ -844,8 +882,8 @@ class LabelShortcutEditorConfigWidget(QgsEditorConfigWidget):
s = ""
labelType: LabelShortcutType = LabelShortcutType.fromConfValue(
config.get(LabelConfigurationKey.LabelType, None)
)
config.get(LabelConfigurationKey.LabelType, None)
)
assert isinstance(labelType, LabelShortcutType)
labelGroup: str = config.get(LabelConfigurationKey.LabelGroup, '')
......@@ -868,14 +906,14 @@ class LabelShortcutEditorWidgetWrapper(QgsEditorWidgetWrapper):
def __init__(self, vl: QgsVectorLayer, fieldIdx: int, editor: QWidget, parent: QWidget):
super(LabelShortcutEditorWidgetWrapper, self).__init__(vl, fieldIdx, editor, parent)
def createWidget(self, parent: QWidget=None) -> QWidget:
def createWidget(self, parent: QWidget = None) -> QWidget:
"""
Create the data input widget
:param parent: QWidget
:return: QLineEdit | QgsDateTimeEdit | QSpinBox
"""
# log('createWidget')
#labelType = self.configLabelType()
# labelType = self.configLabelType()
fieldType = self.field().type()
if fieldType == QVariant.Date:
return QgsDateEdit(parent)
......@@ -969,7 +1007,7 @@ class LabelShortcutEditorWidgetWrapper(QgsEditorWidgetWrapper):
if isinstance(editor, QWidget):
editor.setEnabled(enabled)
#def setFeature(self, feature:QgsFeature):
# def setFeature(self, feature:QgsFeature):
# s = ""
def setValue(self, value):
......@@ -1001,7 +1039,7 @@ class LabelShortcutEditorWidgetWrapper(QgsEditorWidgetWrapper):
elif isinstance(w, (QgsSpinBox, QgsDoubleSpinBox)):
if w.maximum() <= value:
e = int(math.log10(value)) + 1
w.setMaximum(int(10**e))
w.setMaximum(int(10 ** e))
w.setClearValue(value)
w.setValue(value)
elif isinstance(w, QLineEdit):
......@@ -1012,7 +1050,6 @@ class LabelShortcutEditorWidgetWrapper(QgsEditorWidgetWrapper):
def createWidgetConf(labelType: LabelShortcutType,
group: str = None) -> typing.Dict[str, str]:
assert isinstance(labelType, LabelShortcutType)
if group is None:
group = ''
......@@ -1027,7 +1064,6 @@ def createWidgetConf(labelType: LabelShortcutType,
def createWidgetSetup(labelType: LabelShortcutType,
group: str = None
) -> QgsEditorWidgetSetup:
conf = createWidgetConf(labelType, group)
return QgsEditorWidgetSetup(EDITOR_WIDGET_REGISTRY_KEY, conf)
......@@ -1036,11 +1072,11 @@ class LabelShortcutWidgetFactory(QgsEditorWidgetFactory):
"""
A QgsEditorWidgetFactory to create widgets for EOTSV Quick Labeling
"""
@staticmethod
def instance():
return QgsGui.editorWidgetRegistry().factory(EDITOR_WIDGET_REGISTRY_KEY)
@staticmethod
def createWidgetSetup(*args, **kwds) -> QgsEditorWidgetSetup:
return createWidgetSetup(*args, **kwds)
......@@ -1117,7 +1153,6 @@ class LabelShortcutWidgetFactory(QgsEditorWidgetFactory):
return False
labelEditorWidgetFactory = None
......
......@@ -957,8 +957,7 @@ class MapCanvas(QgsMapCanvas):
for (cs, field) in csf:
assert isinstance(cs, ClassificationScheme)
assert isinstance(field, QgsField)
classMenu = m.addMenu('{}:"{}":{}'.format(layer.name(), field.name(), field.typeName()))
classMenu = m.addMenu('{} [{}]'.format(field.name(), field.typeName()))
for classInfo in cs:
assert isinstance(classInfo, ClassInfo)
a = classMenu.addAction('{} "{}"'.format(classInfo.label(), classInfo.name()))
......
......@@ -814,12 +814,17 @@ class TimeSeriesDate(QAbstractTableModel):
assert isinstance(date, np.datetime64)
assert isinstance(sensor, SensorInstrument)
self.mSensor = sensor
self.mDate = date
self.mDOY = DOYfromDatetime64(self.mDate)
self.mSources = []
self.mSensor: SensorInstrument = sensor
self.mDate: np.datetime64 = None
self.mDOY: int = None
self.mSources: typing.List[TimeSeriesSource] = []
self.mMasks = []
self.mTimeSeries = timeSeries
self.mTimeSeries: TimeSeries = timeSeries
self.setDate(date)
def setDate(self, date):
self.mDate = date
self.mDOY = DOYfromDatetime64(date)
def removeSource(self, source: TimeSeriesSource):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment