Newer
Older
sigTemporalProfilesRemoved = pyqtSignal(list)
sigMaxProfilesChanged = pyqtSignal(int)
def __init__(self, timeSeries:TimeSeries, uri=None, name='Temporal Profiles'):
lyrOptions = QgsVectorLayer.LayerOptions(loadDefaultStyle=False, readExtentFromXml=False)
if uri is None:
# create a new, empty backend
# existing_vsi_files = vsiSpeclibs()
existing_vsi_files = []
# todo:
assert isinstance(existing_vsi_files, list)
i = 0
_name = name.replace(' ', '_')
uri = (pathlib.Path(VSI_DIR) / '{}.gpkg'.format(_name)).as_posix()
while not ogr.Open(uri) is None:
uri = (pathlib.Path(VSI_DIR) / '{}{:03}.gpkg'.format(_name, i)).as_posix()
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
drv = ogr.GetDriverByName('GPKG')
assert isinstance(drv, ogr.Driver)
co = ['VERSION=AUTO']
dsSrc = drv.CreateDataSource(uri, options=co)
assert isinstance(dsSrc, ogr.DataSource)
srs = osr.SpatialReference()
srs.ImportFromEPSG(4326)
co = ['GEOMETRY_NAME=geom',
'GEOMETRY_NULLABLE=YES',
'FID=fid'
]
lyr = dsSrc.CreateLayer(name, srs=srs, geom_type=ogr.wkbPoint, options=co)
assert isinstance(lyr, ogr.Layer)
ldefn = lyr.GetLayerDefn()
assert isinstance(ldefn, ogr.FeatureDefn)
dsSrc.FlushCache()
else:
dsSrc = ogr.Open(uri)
assert isinstance(dsSrc, ogr.DataSource)
names = [dsSrc.GetLayerByIndex(i).GetName() for i in range(dsSrc.GetLayerCount())]
i = names.index(name)
lyr = dsSrc.GetLayer(i)
# consistency check
uri2 = '{}|{}'.format(dsSrc.GetName(), lyr.GetName())
assert QgsVectorLayer(uri2).isValid()
super(TemporalProfileLayer, self).__init__(uri2, name, 'ogr', lyrOptions)
"""
uri = 'Point?crs={}'.format(crs.authid())
lyrOptions = QgsVectorLayer.LayerOptions(loadDefaultStyle=False, readExtentFromXml=False)
super(TemporalProfileLayer, self).__init__(uri, name, 'memory', lyrOptions)
self.mProfiles = OrderedDict()
self.mTimeSeries = timeSeries
#symbol = QgsFillSymbol.createSimple({'style': 'no', 'color': 'red', 'outline_color': 'black'})
#self.mLocations.renderer().setSymbol(symbol)
self.mNextID = 1
self.TS = None
fields = QgsFields()
fields.append(createQgsField(FN_ID, self.mNextID))
fields.append(createQgsField(FN_NAME,''))
fields.append(createQgsField(FN_X, 0.0, comment='Longitude'))
fields.append(createQgsField(FN_Y, 0.0, comment='Latitude'))
fields.append(createQgsField(FN_N_TOTAL, 0, comment='Total number of band values'))
fields.append(createQgsField(FN_N_NODATA,0, comment='Total of no-data values.'))
fields.append(createQgsField(FN_N_LOADED, 0, comment='Loaded valid band values.'))
fields.append(createQgsField(FN_N_LOADED_PERCENT,0.0, comment='Loading progress (%)'))
assert self.startEditing()
assert self.dataProvider().addAttributes(fields)
assert self.commitChanges()
self.initConditionalStyles()
self.committedFeaturesAdded.connect(self.onFeaturesAdded)
self.committedFeaturesRemoved.connect(self.onFeaturesRemoved)
def __getitem__(self, slice):
return list(self.mProfiles.values())[slice]
def loadMissingData(self, backgroundProcess=False):
assert isinstance(self.mTimeSeries, TimeSeries)
# Get or create the TimeSeriesProfiles which will store the loaded values
tasks = []
theGeometries = []
# Define which (new) bands need to be loaded for each sensor
LUT_bandIndices = dict()
LUT_bandIndices[sensor] = list(range(sensor.nb))
PL = PixelLoader()
PL.sigPixelLoaded.connect(self.addPixelLoaderResult)
# update new / existing points
for tsd in self.mTimeSeries:
assert isinstance(tsd, TimeSeriesDatum)
requiredIndexKeys = [bandIndex2bandKey(b) for b in requiredIndices]
TPs = []
missingIndices = set()
for TP in self.mProfiles.values():
assert isinstance(TP, TemporalProfile)
dataValues = TP.mData[tsd]
existingKeys = list(dataValues.keys())
missingIdx = [bandKey2bandIndex(k) for k in requiredIndexKeys if k not in existingKeys]
if len(missingIdx) > 0:
TPs.append(TP)
missingIndices.union(set(missingIdx))
if len(TPs) > 0:
theGeometries = [tp.coordinate() for tp in TPs]
theIDs = [tp.id() for tp in TPs]
for pathImg in tsd.sourceUris():
task = PixelLoaderTask(pathImg, theGeometries,
bandIndices=requiredIndices,
temporalProfileIDs=theIDs)
tasks.append(task)
if len(tasks) > 0:
if backgroundProcess:
PL.startLoading(tasks)
else:
import eotimeseriesviewer.pixelloader
tasks = [PixelLoaderTask.fromDump(eotimeseriesviewer.pixelloader.doLoaderTask(None, task.toDump())) for task in tasks]
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
else:
if DEBUG:
print('Data for geometries already loaded')
s = ""
def saveTemporalProfiles(self, pathVector, loadMissingValues=False, sep='\t'):
if pathVector is None or len(pathVector) == 0:
global DEFAULT_SAVE_PATH
if DEFAULT_SAVE_PATH == None:
DEFAULT_SAVE_PATH = 'temporalprofiles.shp'
d = os.path.dirname(DEFAULT_SAVE_PATH)
filters = QgsProviderRegistry.instance().fileVectorFilters()
pathVector, filter = QFileDialog.getSaveFileName(None, 'Save {}'.format(self.name()), DEFAULT_SAVE_PATH,
filter=filters)
if len(pathVector) == 0:
return None
else:
DEFAULT_SAVE_PATH = pathVector
if loadMissingValues:
self.loadMissingData(backgroundProcess=False)
for p in self.mProfiles.values():
assert isinstance(p, TemporalProfile)
p.loadMissingData()
drvName = QgsVectorFileWriter.driverForExtension(os.path.splitext(pathVector)[-1])
QgsVectorFileWriter.writeAsVectorFormat(self, pathVector, 'utf-8', destCRS=self.crs(), driverName=drvName)
pathCSV = os.path.splitext(pathVector)[0] + '.data.csv'
# write a flat list of profiles
csvLines.append(sep.join(['id', 'name', 'sensor', 'date', 'doy', 'sensor'] + ['b{}'.format(b+1) for b in range(nBands)]))
for p in list(self.getFeatures()):
assert isinstance(p, QgsFeature)
fid = p.id()
tp = self.mProfiles.get(fid)
if tp is None:
continue
assert isinstance(tp, TemporalProfile)
name = tp.name()
for tsd, values in tp.mData.items():
assert isinstance(tsd, TimeSeriesDatum)
line = [fid, name, tsd.mSensor.name(), tsd.mDate, tsd.mDOY]
for b in range(tsd.mSensor.nb):
key = 'b{}'.format(b+1)
line.append(values.get(key))
line = ['' if v == None else str(v) for v in line]
line = sep.join([str(l) for l in line])
# write CSV file
with open(pathCSV, 'w', encoding='utf8') as f:
f.write('\n'.join(csvLines))
def timeSeries(self):
"""
Returns the TimeSeries instance.
:return: TimeSeries
"""
return self.mTimeSeries
def onFeaturesAdded(self, layerID, addedFeatures):
"""
Create a TemporalProfile object for each QgsFeature added to the backend QgsVectorLayer
:param layerID:
:param addedFeatures:
:return:
"""
if len(addedFeatures) > 0:
temporalProfiles = []
for feature in addedFeatures:
fid = feature.id()
tp = TemporalProfile(self, fid)
self.mProfiles[fid] = tp
temporalProfiles.append(tp)
if len(temporalProfiles) > 0:
pass
#self.sigTemporalProfilesAdded.emit(temporalProfiles)
def onFeaturesRemoved(self, layerID, removedFIDs):
if len(removedFIDs) > 0:
toRemove = []
for fid in removedFIDs:
toRemove.append(self.mProfiles.pop(fid))
def initConditionalStyles(self):
styles = self.conditionalStyles()
assert isinstance(styles, QgsConditionalLayerStyles)
for fieldName in self.fields().names():
red = QgsConditionalStyle("@value is NULL")
red.setTextColor(QColor('red'))
styles.setFieldStyles(fieldName, [red])
#styles.setRowStyles([red])
def createTemporalProfiles(self, coordinates)->list:
"""
Creates temporal profiles
:param coordinates:
:return:
"""
if not isinstance(coordinates, list):
coordinates = [coordinates]
features = []
for i, coordinate in enumerate(coordinates):
assert isinstance(coordinate, SpatialPoint)
f = QgsFeature(self.fields())
f.setGeometry(QgsGeometry.fromPointXY(coordinate.toCrs(self.crs())))
f.setAttribute(FN_X, coordinate.x())
f.setAttribute(FN_Y, coordinate.y())
f.setAttribute(FN_N_LOADED_PERCENT, 0.0)
f.setAttribute(FN_N_LOADED, 0)
f.setAttribute(FN_N_TOTAL, 0)
f.setAttribute(FN_N_NODATA, 0)
self.mNextID += 1
features.append(f)
b = self.isEditable()
tps_before = list(self.mProfiles.values())
success = self.addFeatures(features)
self.saveEdits(leaveEditable=b)
if success:
assert n+len(features) == self.dataProvider().featureCount()
assert self.dataProvider().featureCount() == len(self.mProfiles)
profiles = [tp for tp in self.mProfiles.values() if tp not in tps_before]
for p in profiles:
p.updateLoadingStatus()
return profiles
else:
def saveEdits(self, leaveEditable=True, triggerRepaint=True):
"""
function to save layer changes-
:param layer:
:param leaveEditable:
:param triggerRepaint:
"""
if not self.isEditable():
return
if not self.commitChanges():
self.commitErrors()
if leaveEditable:
self.startEditing()
if triggerRepaint:
self.triggerRepaint()
def addMissingFields(self, fields):
missingFields = []
for field in fields:
assert isinstance(field, QgsField)
i = self.dataProvider().fieldNameIndex(field.name())
if i == -1:
missingFields.append(field)
if len(missingFields) > 0:
b = self.isEditable()
self.startEditing()
self.dataProvider().addAttributes(missingFields)
self.saveEdits(leaveEditable=b)
def __len__(self):
def __iter__(self):
r = QgsFeatureRequest()
for f in self.getFeatures(r):
yield self.mProfiles[f.id()]
def __contains__(self, item):
return item in self.mProfiles.values()
def temporalProfileToLocationFeature(self, tp:TemporalProfile):
self.mLocations.selectByIds([tp.id()])
for f in self.mLocations.selectedFeatures():
assert isinstance(f, QgsFeature)
return f
return None
def fromSpatialPoint(self, spatialPoint):
""" Tests if a Temporal Profile already exists for the given spatialPoint"""
for p in list(self.mProfiles.values()):
assert isinstance(p, TemporalProfile)
if p.coordinate() == spatialPoint:
return p
"""
spatialPoint = spatialPoint.toCrs(self.crs())
unit = QgsUnitTypes.toAbbreviatedString(self.crs().mapUnits()).lower()
x = spatialPoint.x() + 0.00001
y = spatialPoint.y() + 0.
if 'degree' in unit:
dx = dy = 0.000001
else:
dx = dy = 0.1
rect = QgsRectangle(x-dx,y-dy, x+dy,y+dy)
for f in self.getFeatures(rect):
return self.mProfiles[f.id()]
return None
def removeTemporalProfiles(self, temporalProfiles):
"""
Removes temporal profiles from this collection
:param temporalProfile: TemporalProfile
"""
if isinstance(temporalProfiles, TemporalProfile):
temporalProfiles = [temporalProfiles]
assert isinstance(temporalProfiles, list)
temporalProfiles = [tp for tp in temporalProfiles if isinstance(tp, TemporalProfile) and tp.id() in self.mProfiles.keys()]
if len(temporalProfiles) > 0:
b = self.isEditable()
assert self.startEditing()
fids = [tp.mID for tp in temporalProfiles]
self.deleteFeatures(fids)
self.saveEdits(leaveEditable=b)
self.sigTemporalProfilesRemoved.emit(temporalProfiles)
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
def loadCoordinatesFromOgr(self, path):
"""Loads the TemporalProfiles for vector geometries in data source 'path' """
if path is None:
filters = QgsProviderRegistry.instance().fileVectorFilters()
defDir = None
if isinstance(DEFAULT_SAVE_PATH, str) and len(DEFAULT_SAVE_PATH) > 0:
defDir = os.path.dirname(DEFAULT_SAVE_PATH)
path, filter = QFileDialog.getOpenFileName(directory=defDir, filter=filters)
if isinstance(path, str) and len(path) > 0:
sourceLyr = QgsVectorLayer(path)
nameAttribute = None
fieldNames = [n.lower() for n in sourceLyr.fields().names()]
for candidate in ['name', 'id']:
if candidate in fieldNames:
nameAttribute = sourceLyr.fields().names()[fieldNames.index(candidate)]
break
if len(self.timeSeries()) == 0:
sourceLyr.selectAll()
else:
extent = self.timeSeries().maxSpatialExtent(sourceLyr.crs())
sourceLyr.selectByRect(extent)
newProfiles = []
for feature in sourceLyr.selectedFeatures():
assert isinstance(feature, QgsFeature)
geom = feature.geometry()
if isinstance(geom, QgsGeometry):
point = geom.centroid().constGet()
try:
TPs = self.createTemporalProfiles(SpatialPoint(sourceLyr.crs(), point))
for TP in TPs:
if nameAttribute:
name = feature.attribute(nameAttribute)
else:
name = 'FID {}'.format(feature.id())
TP.setName(name)
newProfiles.append(TP)
except Exception as ex:
print(ex)
def addPixelLoaderResult(self, d):
assert isinstance(d, PixelLoaderTask)
if d.success():
for fid in d.temporalProfileIDs:
TP = self.mProfiles.get(fid)

benjamin.jakimow@geo.hu-berlin.de
committed
if isinstance(TP, TemporalProfile):
TP.pullDataUpdate(d)
else:

benjamin.jakimow@geo.hu-berlin.de
committed
s = ""
def clear(self):
#todo: remove TS Profiles
#self.mTemporalProfiles.clear()
#self.sensorPxLayers.clear()
pass
class TemporalProfileTableFilterModel(QgsAttributeTableFilterModel):
def __init__(self, sourceModel, parent=None):
dummyCanvas = QgsMapCanvas(parent)
dummyCanvas.setDestinationCrs(DEFAULT_CRS)
dummyCanvas.setExtent(QgsRectangle(-180,-90,180,90))
super(TemporalProfileTableFilterModel, self).__init__(dummyCanvas, sourceModel, parent=parent)
self.mDummyCanvas = dummyCanvas
#self.setSelectedOnTop(True)
class TemporalProfileTableModel(QgsAttributeTableModel):
#sigPlotStyleChanged = pyqtSignal(SpectralProfile)
#sigAttributeRemoved = pyqtSignal(str)
#sigAttributeAdded = pyqtSignal(str)
AUTOGENERATES_COLUMNS = [FN_Y, FN_X, FN_N_LOADED, FN_N_TOTAL, FN_N_NODATA, FN_ID, FN_N_LOADED_PERCENT]
def __init__(self, temporalProfileLayer=None, parent=None):
if temporalProfileLayer is None:
temporalProfileLayer = TemporalProfileLayer()
cache = QgsVectorLayerCache(temporalProfileLayer, 1000)
super(TemporalProfileTableModel, self).__init__(cache, parent)
self.mTemporalProfileLayer = temporalProfileLayer
self.mCache = cache
assert self.mCache.layer() == self.mTemporalProfileLayer
self.loadLayer()
def columnNames(self):
return self.mTemporalProfileLayer.fields().names()
def feature(self, index):
id = self.rowToId(index.row())
f = self.layer().getFeature(id)
return f
def temporalProfile(self, index):
feature = self.feature(index)
return self.mTemporalProfileLayer.temporalProfileFromFeature(feature)
def data(self, index, role=Qt.DisplayRole):
Returns Temporal Profile Layer values
:param index: QModelIndex
:param role: enum Qt.ItemDataRole
:return: value
"""
if role is None or not index.isValid():
return None
result = super(TemporalProfileTableModel, self).data(index, role=role)
return result
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
def setData(self, index, value, role=None):
"""
Sets Temporal Profile Data.
:param index: QModelIndex()
:param value: value to set
:param role: role
:return: True | False
"""
if role is None or not index.isValid():
return False
f = self.feature(index)
result = False
if value == None:
value = QVariant()
cname = self.columnNames()[index.column()]
if role == Qt.EditRole and cname not in TemporalProfileTableModel.AUTOGENERATES_COLUMNS:
i = f.fieldNameIndex(cname)
if f.attribute(i) == value:
return False
b = self.mTemporalProfileLayer.isEditable()
self.mTemporalProfileLayer.startEditing()
self.mTemporalProfileLayer.changeAttributeValue(f.id(), i, value)
self.mTemporalProfileLayer.saveEdits(leaveEditable=b)
result = True
#f = self.layer().getFeature(profile.id())
#i = f.fieldNameIndex(SpectralProfile.STYLE_FIELD)
#self.layer().changeAttributeValue(f.id(), i, value)
#result = super().setData(self.index(index.row(), self.mcnStyle), value, role=Qt.EditRole)
#if not b:
# self.layer().commitChanges()
if result:
self.dataChanged.emit(index, index, [role])
else:
result = super().setData(index, value, role=role)
return result
def headerData(self, section:int, orientation:Qt.Orientation, role:int):
data = super(TemporalProfileTableModel, self).headerData(section, orientation, role)
if role == Qt.ToolTipRole and orientation == Qt.Horizontal:
#add the field comment to column description
field = self.layer().fields().at(section)
assert isinstance(field, QgsField)
comment = field.comment()
if len(comment) > 0:
data = re.sub('</p>$', ' <i>{}</i></p>'.format(comment), data)
return data
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
def supportedDragActions(self):
return Qt.CopyAction | Qt.MoveAction
def supportedDropActions(self):
return Qt.CopyAction | Qt.MoveAction
def supportedDragActions(self):
return Qt.CopyAction
def supportedDropActions(self):
return Qt.CopyAction
def flags(self, index):
if index.isValid():
columnName = self.columnNames()[index.column()]
flags = super(TemporalProfileTableModel, self).flags(index) | Qt.ItemIsSelectable
#if index.column() == 0:
# flags = flags | Qt.ItemIsUserCheckable
if columnName in TemporalProfileTableModel.AUTOGENERATES_COLUMNS:
flags = flags ^ Qt.ItemIsEditable
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
return flags
return None
class TemporalProfileFeatureSelectionManager(QgsIFeatureSelectionManager):
def __init__(self, layer, parent=None):
s =""
super(TemporalProfileFeatureSelectionManager, self).__init__(parent)
assert isinstance(layer, QgsVectorLayer)
self.mLayer = layer
self.mLayer.selectionChanged.connect(self.selectionChanged)
def layer(self):
return self.mLayer
def deselect(self, ids):
if len(ids) > 0:
selected = [id for id in self.selectedFeatureIds() if id not in ids]
self.mLayer.deselect(ids)
self.selectionChanged.emit(selected, ids, True)
def select(self, ids):
self.mLayer.select(ids)
def selectFeatures(self, selection, command):
super(TemporalProfileFeatureSelectionManager, self).selectF
s = ""
def selectedFeatureCount(self):
return self.mLayer.selectedFeatureCount()
def selectedFeatureIds(self):
return self.mLayer.selectedFeatureIds()
def setSelectedFeatures(self, ids):
self.mLayer.selectByIds(ids)
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
class TemporalProfileTableView(QgsAttributeTableView):
def __init__(self, parent=None):
super(TemporalProfileTableView, self).__init__(parent)
#self.setSelectionBehavior(QAbstractItemView.SelectRows)
#self.setSelectionMode(QAbstractItemView.SingleSelection)
self.horizontalHeader().setSectionsMovable(True)
self.willShowContextMenu.connect(self.onWillShowContextMenu)
self.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
self.mSelectionManager = None
def setModel(self, filterModel):
super(TemporalProfileTableView, self).setModel(filterModel)
self.mSelectionManager = TemporalProfileFeatureSelectionManager(self.model().layer())
self.setFeatureSelectionManager(self.mSelectionManager)
#self.selectionModel().selectionChanged.connect(self.onSelectionChanged)
self.mContextMenuActions = []
def setContextMenuActions(self, actions:list):
self.mContextMenuActions = actions
#def contextMenuEvent(self, event):
def onWillShowContextMenu(self, menu, index):
assert isinstance(menu, QMenu)
assert isinstance(index, QModelIndex)
featureIDs = self.temporalProfileLayer().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())
for a in self.mContextMenuActions:
menu.addAction(a)
for a in self.actions():
menu.addAction(a)
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
def temporalProfileLayer(self):
return self.model().layer()
def fidsToIndices(self, fids):
"""
Converts feature ids into FilterModel QModelIndices
:param fids: [list-of-int]
:return:
"""
if isinstance(fids, int):
fids = [fids]
assert isinstance(fids, list)
fmodel = self.model()
indices = [fmodel.fidToIndex(id) for id in fids]
return [fmodel.index(idx.row(), 0) for idx in indices]
def onRemoveFIDs(self, fids):
layer = self.temporalProfileLayer()
assert isinstance(layer, TemporalProfileLayer)
b = layer.isEditable()
layer.startEditing()
layer.deleteFeatures(fids)
layer.saveEdits(leaveEditable=b)
def dropEvent(self, event):
assert isinstance(event, QDropEvent)
mimeData = event.mimeData()
if self.model().rowCount() == 0:
index = self.model().createIndex(0,0)
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
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