diff --git a/test/test_stackedbandinput.py b/test/test_stackedbandinput.py index ab8d1c54601997e0e65eec1f827ce91121d1d657..c40cd5a29f30afeadfa46addd9c4a007e9dde1d2 100644 --- a/test/test_stackedbandinput.py +++ b/test/test_stackedbandinput.py @@ -32,21 +32,86 @@ QGIS_APP = initQgisApplication(qgisResourceDir=resourceDir) class testclassDialogTest(unittest.TestCase): """Test rerources work.""" + @classmethod + def setUpClass(cls): + + cls.srcDir = r'F:\Temp\EOTSV_Dev\DF' + cls.stackFiles = file_search(cls.srcDir, '*.tif') + def setUp(self): """Runs before each test.""" pass - def tearDown(self): """Runs after each test.""" pass + def createTestDatasets(self): + + vsiDir = '/vsimem/tmp' + + ns = 50 + nl = 100 + + r1 = np.arange('2000-01-01', '2001-06-14', step=np.timedelta64(16, 'D'), dtype=np.datetime64) + r2 = np.arange('2000-01-01', '2001-06-14', step=np.timedelta64(8, 'D'), dtype=np.datetime64) + drv = gdal.GetDriverByName('ENVI') + assert isinstance(drv, gdal.Driver) + for i, r in enumerate([r1, r2]): + p = '{}stack{}.bsq'.format(vsiDir, i+1) + p. + + datasets = [] + + pass + + def test_outputmodel(self): + + m = OutputImageModel() + m.setOutputDir('/vsimem/dub') + m.setOutputPrefix('myPrefix') + + stackInfos = [InputStackInfo(f) for f in self.stackFiles] + m.setMultiStackSources(stackInfos) + + self.assertTrue(len(m) > 0) + + + outInfo = m.mOutputImages[0] + self.assertIsInstance(outInfo, OutputVRTDescription) + + xml = m.vrtXML(outInfo) + self.assertIsInstance(xml, str) + eTree = m.vrtXML(outInfo, asElementTree=True) + self.assertIsInstance(eTree, ElementTree.ElementTree) + + + + + + def test_dateparsing(self): + + dsDates = gdal.OpenEx(self.stackFiles[1], allowed_drivers=['ENVI']) + + #dsDates = gdal.Open(self.stackFiles[1]) + dates = datesFromDataset(dsDates) + self.assertEqual(len(dates), dsDates.RasterCount) + + + dsNoDates = gdal.OpenEx(self.stackFiles[0], allowed_drivers=['ENVI']) + dates = datesFromDataset(dsNoDates) + self.assertEqual(len(dates), 0) + + s = "" def test_dialog(self): d = StackedBandInputDialog() - d.addSources([Img_2014_05_07_LC82270652014127LGN00_BOA, Img_2014_06_16_LE72270652014167CUB00_BOA]) + d.addSources(self.stackFiles) d.show() QGIS_APP.exec_() pass + + + if __name__ == "__main__": unittest.main() diff --git a/timeseriesviewer/dateparser.py b/timeseriesviewer/dateparser.py index 110de79f58a5090da1a0c5ea8c370aa3b068371c..b55da28783e1d7048d8da4d82f193035ff736db5 100644 --- a/timeseriesviewer/dateparser.py +++ b/timeseriesviewer/dateparser.py @@ -1,9 +1,10 @@ -import os, re, logging +import os, re, logging, datetime from osgeo import gdal import numpy as np from qgis import * -logger = logging.getLogger(__file__) +from qgis.PyQt.QtCore import QDate + #regular expression. compile them only once @@ -19,6 +20,7 @@ regYYYYMMDD = re.compile(r'(?P<year>(19|20)\d\d)(?P<hyphen>-?)(?P<month>1[0-2]|0 regMissingHypen = re.compile(r'^\d{8}') regYYYYMM = re.compile(r'([0-9]{4})-(1[0-2]|0[1-9])') regYYYYDOY = re.compile(r'(?P<year>(19|20)\d\d)-?(?P<day>36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9])') +regDecimalYear = re.compile(r'(?P<year>(19|20)\d\d)\.(?P<datefraction>\d\d\d)') def matchOrNone(regex, text): match = regex.search(text) @@ -27,7 +29,52 @@ def matchOrNone(regex, text): else: return None -def extractDateTimeGroup(text): +def dateDOY(date): + if isinstance(date, np.datetime64): + date = date.astype(datetime.date) + return date.timetuple().tm_yday + +def daysPerYear(year): + + if isinstance(year, np.datetime64): + year = year.astype(datetime.date) + if isinstance(year, datetime.date): + year = year.timetuple().tm_year + + return dateDOY(datetime.date(year=year, month=12, day=31)) + +def num2date(n, dt64=True, qDate=False): + n = float(n) + if n < 1: + n += 1 + + year = int(n) + fraction = n - year + yearDuration = daysPerYear(year) + yearElapsed = fraction * yearDuration + + import math + doy = round(yearElapsed) + if doy < 1: + doy = 1 + try: + date = datetime.date(year, 1, 1) + datetime.timedelta(days=doy-1) + except: + s = "" + if qDate: + return QDate(date.year, date.month, date.day) + if dt64: + return np.datetime64(date) + else: + return date + + +def extractDateTimeGroup(text:str)->np.datetime64: + """ + Extracts a date-time-group from a text string + :param text: a string + :return: numpy.datetime64 in case of success, or None + """ match = regISODate1.search(text) if match: matchedText = match.group() @@ -47,6 +94,14 @@ def extractDateTimeGroup(text): if match: return np.datetime64(match.group()) + match = regDecimalYear.search(text) + if match: + year = float(match.group('year')) + df = float(match.group('datefraction')) + num = match.group() + return num2date(num) + + return None def datetime64FromYYYYMMDD(yyyymmdd): diff --git a/timeseriesviewer/stackedbandinput.py b/timeseriesviewer/stackedbandinput.py index 835e255803884477bd1949f0e9fc0932433c464b..95e17bdb4f9de5f9eb4df55d084331f4bf1535ba 100644 --- a/timeseriesviewer/stackedbandinput.py +++ b/timeseriesviewer/stackedbandinput.py @@ -21,6 +21,7 @@ """ import os, re, tempfile, pickle, copy, shutil, locale, uuid, csv, io +from xml.etree import ElementTree from collections import OrderedDict from qgis.core import * from qgis.gui import * @@ -36,22 +37,77 @@ from pyqtgraph.graphicsItems.PlotItem import PlotItem import pyqtgraph.functions as fn import numpy as np from osgeo import gdal, gdal_array - +import numpy as np from timeseriesviewer.utils import * -#from timeseriesviewer.virtualrasters import * +from timeseriesviewer.virtualrasters import * from timeseriesviewer.models import * +from timeseriesviewer.dateparser import * from timeseriesviewer.plotstyling import PlotStyle, PlotStyleDialog, MARKERSYMBOLS2QGIS_SYMBOLS import timeseriesviewer.mimedata as mimedata + +def datesFromDataset(dataset:gdal.Dataset)->list: + + nb = dataset.RasterCount + + def checkDates(dateList): + if not len(dateList) == nb: + return False + for d in dateList: + if not isinstance(d, np.datetime64): + return False + return True + + searchedKeys = [] + searchedKeys.append(re.compile('aquisition[ ]*dates$', re.I)) + searchedKeys.append(re.compile('observation[ ]*dates$', re.I)) + searchedKeys.append(re.compile('wavelength$', re.I)) + + #1. Check Metadata + for domain in dataset.GetMetadataDomainList(): + domainData = dataset.GetMetadata_Dict(domain) + assert isinstance(domainData, dict) + + for key, values in domainData.items(): + for regex in searchedKeys: + if regex.search(key.strip()): + values = re.sub('[{}]', '', values) + values = values.split(',') + dateValues = [extractDateTimeGroup(t) for t in values] + if checkDates(dateValues): + return dateValues + + + #2. Check Band Names + bandDates = [extractDateTimeGroup(dataset.GetRasterBand(b+1).GetDescription()) for b in range(nb)] + bandDates = [b for b in bandDates if isinstance(b, np.datetime64)] + if checkDates(bandDates): + return bandDates + + return [] + class InputStackInfo(object): def __init__(self, dataset): - if not isinstance(dataset, gdal.Dataset): - dataset = gdal.Open(dataset) + if isinstance(dataset, str): + #test ENVI header first + basename = os.path.splitext(dataset)[0] + + if os.path.isfile(basename+'.hdr'): + ds = gdal.OpenEx(dataset, allowed_drivers=['ENVI']) + if not isinstance(ds, gdal.Dataset): + ds = gdal.Open(dataset) + if not isinstance(ds, gdal.Dataset): + raise Exception('Unable to open {}'.format(dataset)) + + dataset = ds + del ds + assert isinstance(dataset, gdal.Dataset) self.mMetadataDomains = dataset.GetMetadataDomainList() self.mMetaData = OrderedDict() + for domain in self.mMetadataDomains: self.mMetaData[domain] = dataset.GetMetadata_Dict(domain) @@ -64,11 +120,58 @@ class InputStackInfo(object): self.path = dataset.GetFileList()[0] - self.bandName = os.path.splitext(os.path.basename(self.path))[0] + self.outputBandName = os.path.splitext(os.path.basename(self.path))[0] + + + self.bandnames = [] + self.nodatavalues = [] + for b in range(self.nb): + band = dataset.GetRasterBand(b+1) + assert isinstance(band, gdal.Band) + self.bandnames.append(band.GetDescription()) + self.nodatavalues.append(band.GetNoDataValue()) + + + self.mDates = datesFromDataset(dataset) + + def __len__(self): + return len(self.mDates) + + def dates(self)->list: + """Returns a list of dates""" + return self.mDates + def structure(self): return (self.ns, self.nl, self.nb, self.gt, self.wkt) + def wavelength(self): + return self.mMetaData[''].get('wavelength') + + def setWavelength(self, wl): + self.mMetaData['']['wavelength'] = wl + + +class OutputVRTDescription(object): + """ + Descrbies an output VRT + """ + + def __init__(self, path:str, date:np.datetime64): + super(OutputVRTDescription, self).__init__() + self.mPath = path + self.mDate = date + + + def setPath(self, path:str): + self.mPath = path + + def xml(self): + """ + Create the VRT XML + :return: + """ + class InputStackTableModel(QAbstractTableModel): @@ -78,16 +181,33 @@ class InputStackTableModel(QAbstractTableModel): super(InputStackTableModel, self).__init__(parent) self.mStackImages = [] - self.cnFile = 'File' - self.cn_fileproperties = 'Properties' - self.cn_ns = 'Samples' - self.cn_nl = 'Lines' - self.cn_nb = 'Bands' - self.cb_name = 'Band Name' + self.cn_source = 'Source' + self.cn_dates = 'Dates' + self.cn_crs = 'GT + CRS' + self.cn_ns = 'ns' + self.cn_nl = 'nl' + self.cn_nb = 'nb' + self.cn_name = 'Band Name' self.cn_wl = 'Wavelength' - self.mColumnNames = [self.cnFile, self.cn_fileproperties, self.cn_wl] + self.mColumnNames = [self.cn_source, self.cn_dates, self.cn_name, self.cn_wl, self.cn_ns, self.cn_nl, self.cn_nb, self.cn_crs] + + self.mColumnTooltips = {} + self.mColumnTooltips[self.cn_source] = 'Stack source uri / file path' + self.mColumnTooltips[self.cn_crs] = 'Geo-Transformation + Coordinate Reference System' + self.mColumnTooltips[self.cn_ns] = 'Number of samples / pixel in horizontal direction' + self.mColumnTooltips[self.cn_nl] = 'Number of lines / pixel in vertical direction' + self.mColumnTooltips[self.cn_nb] = 'Number of bands' + self.mColumnTooltips[self.cn_name] = 'Prefix of band name in output image' + self.mColumnTooltips[self.cn_wl] = 'Wavelength in output image' + self.mColumnTooltips[self.cn_dates] = 'Identified dates' + + def __len__(self): + return len(self.mStackImages) + + def __iter__(self): + return iter(self.mStackImages) - def columnName(self, i): + def columnName(self, i) -> str: if isinstance(i, QModelIndex): i = i.column() return self.mColumnNames[i] @@ -97,7 +217,7 @@ class InputStackTableModel(QAbstractTableModel): columnName = self.columnName(index) flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if columnName in [self.cn_name, self.cn_wl]: #allow check state - flags = flags | Qt.ItemIsUserCheckable + flags = flags | Qt.ItemIsEditable return flags #return item.qt_flags(index.column()) @@ -106,8 +226,12 @@ class InputStackTableModel(QAbstractTableModel): def headerData(self, col, orientation, role): if Qt is None: return None - if orientation == Qt.Horizontal and role == Qt.DisplayRole: - return self.mColumnNames[col] + if orientation == Qt.Horizontal: + cname = self.mColumnNames[col] + if role == Qt.DisplayRole: + return cname + elif role == Qt.ToolTipRole: + return self.mColumnTooltips.get(cname) elif orientation == Qt.Vertical and role == Qt.DisplayRole: return col return None @@ -127,14 +251,7 @@ class InputStackTableModel(QAbstractTableModel): if not isinstance(paths, list): paths = [paths] - infos = [] - for p in paths: - assert isinstance(p, str) - ds = gdal.Open(p) - if isinstance(ds, gdal.Dataset): - info = InputStackInfo(ds) - infos.append(info) - + infos = [InputStackInfo(p) for p in paths] if len(infos) > 0: self.beginInsertRows(QModelIndex(), i, i+len(infos)-1) @@ -142,8 +259,19 @@ class InputStackTableModel(QAbstractTableModel): self.mStackImages.insert(i+j, info) self.endInsertRows() + def removeSources(self, stackInfos:list): + + for stackInfo in stackInfos: + assert stackInfo in self.mStackImages + + for stackInfo in stackInfos: + assert isinstance(stackInfo, InputStackInfo) + idx = self.info2index(stackInfo) + self.beginRemoveRows(QModelIndex(), idx.row(), idx.row()) + self.mStackImages.remove(stackInfo) + self.endRemoveRows() def isValid(self): l = len(self.mStackImages) @@ -155,45 +283,313 @@ class InputStackTableModel(QAbstractTableModel): #all input stacks need to have the same characteristic for stackInfo in self.mStackImages[1:]: assert isinstance(stackInfo, InputStackInfo) + if not ref.dates() == stackInfo.dates(): + return False if not ref.structure() == stackInfo.structure(): return False return True - def data(self, index: QModelIndex, role: int): + def index2info(self, index:QModelIndex) -> InputStackInfo: + return self.mStackImages[index.row()] + def info2index(self, info:InputStackInfo) -> QModelIndex: + r = self.mStackImages.index(info) + return self.createIndex(r,0, info) + def data(self, index: QModelIndex, role: int): if not index.isValid(): return None - info = self.mStackImages[index.column()] + info = self.mStackImages[index.row()] assert isinstance(info, InputStackInfo) cname = self.columnName(index) - if role == Qt.DisplayRole: - if cname == self.cnFile: + + if role in [Qt.DisplayRole, Qt.ToolTipRole]: + if cname == self.cn_source: return info.path - elif cname == self.cn_fileproperties: - return str(info.structure()) + if cname == self.cn_dates: + dates = info.dates() + if role == Qt.DisplayRole: + return len(dates) + if role == Qt.ToolTipRole: + if len(dates) == 0: + return 'No dates identified. Can not use this image as input' + else: + if len(dates) > 11: + dates = dates[0:10] + ['...'] + return '\n'.join([str(d) for d in dates]) + + + if cname == self.cn_ns: + return info.ns + if cname == self.cn_nl: + return info.nl + if cname == self.cn_nb: + return info.nb + if cname == self.cn_crs: + return '{} {}'.format(info.gt, info.wkt) elif cname == self.cn_wl: - return str(info.structure()) - elif cname == self.cb_name: - return info.bandName + return info.mMetaData[''].get('wavelength') + elif cname == self.cn_name: + return info.outputBandName + + if role == Qt.EditRole: + if cname == self.cn_wl: + return info.mMetaData[''].get('wavelength') + elif cname == self.cn_name: + return info.outputBandName + if role == Qt.BackgroundColorRole: + if cname in [self.cn_name, self.cn_wl]: + return QColor('yellow') return None + def setData(self, index: QModelIndex, value, role: int): + + if not index.isValid(): + return None + + info = self.index2info(index) + cname = self.columnName(index) + + changed = False + if role == Qt.EditRole: + if cname == self.cn_name: + info.outputBandName = value + changed = True + elif cname == self.cn_wl: + info.setWavelength(value) + changed = True + if changed: + self.dataChanged.emit(index, index) + return changed class OutputImageModel(QAbstractTableModel): def __init__(self, parent=None): - pass + super(OutputImageModel, self).__init__(parent) + self.cn_uri = 'Path' + self.cn_nb = 'nb' + self.cn_date = 'Date' + self.mOutputImages = [] + self.mColumnNames = [self.cn_date, self.cn_nb, self.cn_uri] + self.mColumnTooltips = {} + self.mColumnTooltips[self.cn_nb] = 'Number of bands' + self.mColumnTooltips[self.cn_uri] = 'Output location' + self.masterVRT_DateLookup = {} + self.masterVRT_SourceBandTemplates = {} + self.masterVRT_InputStacks = None + self.masterVRT_XML = None + self.masterVRT_nb = None + self.mOutputDir = '/vsimem/' + self.mOutputPrefix = 'date' - def outputVRTs(self): - pass + def headerData(self, col, orientation, role): + if Qt is None: + return None + if orientation == Qt.Horizontal: + cname = self.mColumnNames[col] + if role == Qt.DisplayRole: + return cname + elif role == Qt.ToolTipRole: + return self.mColumnTooltips.get(cname) + elif orientation == Qt.Vertical and role == Qt.DisplayRole: + return col + return None + + def createVRTUri(self, date:np.datetime64): + + path = os.path.join(self.mOutputDir, self.mOutputPrefix) + path = '{}{}.vrt'.format(path, date) + + return path + + def clearOutputs(self): + self.beginRemoveRows(QModelIndex(), 0, self.rowCount() - 1) + self.mOutputImages = [] + self.endRemoveRows() + + def setMultiStackSources(self, listOfInputStacks:list, nextBand:int=1, nextDate:int=1): + + self.clearOutputs() + if len(listOfInputStacks) == 0: + return + if listOfInputStacks is None or len(listOfInputStacks) == 0: + return + + for s in listOfInputStacks: + assert isinstance(s, InputStackInfo) + + listOfInputStacks = [s for s in listOfInputStacks if len(s) > 0] + numberOfOutputVRTBands = len(listOfInputStacks) + self.masterVRT_DateLookup.clear() + self.masterVRT_InputStacks = listOfInputStacks + self.masterVRT_SourceBandTemplates.clear() + dates = set() + for s in listOfInputStacks: + for d in s.dates(): + dates.add(d) + dates = sorted(list(dates)) + + #create a LUT to get the stack indices for a related date (not each stack might contain a band for each date) + for d in dates: + self.masterVRT_DateLookup[d] = [] + for stackIndex, s in enumerate(listOfInputStacks): + for bandIndex, date in enumerate(s.dates()): + self.masterVRT_DateLookup[date].append((stackIndex, bandIndex)) + + #create VRT Template XML + VRT = VRTRaster() + wavelength = [] + for stackIndex, stack in enumerate(listOfInputStacks): + assert isinstance(stack, InputStackInfo) + vrtBand = VRTRasterBand() + vrtBand.setName(stack.outputBandName) + vrtSrc = VRTRasterInputSourceBand(stack.path, 0) + vrtBand.addSource(vrtSrc) + wavelength.append(stack.wavelength()) + VRT.addVirtualBand(vrtBand) + + pathVSITmp = '/vsimem/temp.vrt' + dsVRT = VRT.saveVRT(pathVSITmp) + dsVRT.SetMetadataItem('acquisition date', 'XML_REPLACE_DATE') + + if None not in wavelength: + dsVRT.SetMetadataItem('wavelength', ','.join(str(wl) for wl in wavelength)) + dsVRT.SetMetadataItem('wavelength units', 'Nanometers') + + dsVRT.FlushCache() + drv = dsVRT.GetDriver() + masterVRT_XML = read_vsimem(pathVSITmp).decode('utf-8') + drv.Delete(pathVSITmp) + outputVRTs = [] + + + eTree = ElementTree.fromstring(masterVRT_XML) + for iBand, elemBand in enumerate(eTree.findall('VRTRasterBand')): + sourceElements = elemBand.findall('ComplexSource') + elemBand.findall('SimpleSource') + assert len(sourceElements) == 1 + self.masterVRT_SourceBandTemplates[iBand] = copy.deepcopy(sourceElements[0]) + elemBand.remove(sourceElements[0]) + + for date in dates: + assert isinstance(date, np.datetime64) + path = self.createVRTUri(date) + outputDescription = OutputVRTDescription(path, date) + outputVRTs.append(outputDescription) + + self.masterVRT_XML = eTree + + + self.beginInsertRows(QModelIndex(), 0, len(outputVRTs)-1) + self.mOutputImages = outputVRTs[:] + self.endInsertRows() + + def setOutputDir(self, path:str): + self.mOutputDir = path + self.updateOutputURIs() + + def setOutputPrefix(self, basename:str): + self.mOutputPrefix = basename + self.updateOutputURIs() + + def updateOutputURIs(self): + c = self.mColumnNames.index(self.cn_uri) + ul = self.createIndex(0, c) + lr = self.createIndex(self.rowCount()-1, c) + + for outputVRT in self: + assert isinstance(outputVRT, OutputVRTDescription) + outputVRT.setPath(self.createVRTUri(outputVRT.mDate)) + self.dataChanged.emit(ul, lr) + + + def __len__(self): + return len(self.mOutputImages) + + def __iter__(self): + return iter(self.mOutputImages) + + def rowCount(self, parent=None) -> int: + return len(self.mOutputImages) + + def columnCount(self, parent=None) -> int: + return len(self.mColumnNames) + + def columnName(self, i) -> str: + if isinstance(i, QModelIndex): + i = i.column() + return self.mColumnNames[i] + + def columnIndex(self, columnName:str)-> QModelIndex: + c = self.mColumnNames.index(columnName) + return self.createIndex(0, c) + + def index2vrt(self, index:QModelIndex) -> OutputVRTDescription: + return self.mOutputImages[index.row()] + + def vrt2index(self, vrt:OutputVRTDescription) -> QModelIndex: + i = self.mOutputImages[vrt] + return self.createIndex(i, 0, vrt) + + def data(self, index: QModelIndex, role: int): + + if not index.isValid(): + return None + + cname = self.columnName(index) + vrt = self.index2vrt(index) + if role in [Qt.DisplayRole, Qt.ToolTipRole]: + if cname == self.cn_uri: + return vrt.mPath + if cname == self.cn_nb: + return self.masterVRT_nb + if cname == self.cn_date: + return str(vrt.mDate) + + def vrtXML(self, outputDefinition:OutputVRTDescription, asElementTree=False) -> str: + """ + Create the VRT XML related to an outputDefinition + :param outputDefinition: + :return: str + """ + + # re.search(tmpXml, '<MDI key='>') + + # xml = copy.deepcopy(eTree) + if self.masterVRT_XML is None: + return None + #xmlTree = ElementTree.fromstring(self.masterVRT_XML) + xmlTree = copy.deepcopy(self.masterVRT_XML) + + # set metadata + for elem in xmlTree.findall('Metadata/MDI'): + if elem.attrib['key'] == 'acquisition date': + elem.text = str(outputDefinition.mDate) + + # insert required rasterbands + requiredBands = self.masterVRT_DateLookup[outputDefinition.mDate] + + xmlVRTBands = xmlTree.findall('VRTRasterBand') + + for t in requiredBands: + stackIndex, stackBandIndex = t + + stackSourceXMLTemplate = copy.deepcopy(self.masterVRT_SourceBandTemplates[stackIndex]) + stackSourceXMLTemplate.find('SourceBand').text = str(stackBandIndex+1) + xmlVRTBands[stackIndex].append(stackSourceXMLTemplate) + + if asElementTree: + return xmlTree + else: + return ElementTree.tostring(xmlTree).decode('utf-8') + @@ -207,13 +603,31 @@ class StackedBandInputDialog(QDialog, loadUI('stackedinputdatadialog.ui')): self.tableModelInputStacks = InputStackTableModel() + self.tableModelInputStacks.rowsInserted.connect(self.updateOutputs) + self.tableModelInputStacks.dataChanged.connect(self.updateOutputs) + self.tableModelInputStacks.rowsRemoved.connect(self.updateOutputs) self.tableViewSourceStacks.setModel(self.tableModelInputStacks) + + self.tableModelOutputImages = OutputImageModel() + self.tableViewOutputImages.setModel(self.tableModelOutputImages) + + + self.tbFilePrefix.textChanged.connect(self.tableModelOutputImages.setOutputPrefix) + self.tbFilePrefix.setText('img') sm = self.tableViewSourceStacks.selectionModel() assert isinstance(sm, QItemSelectionModel) sm.selectionChanged.connect(self.onSourceStackSelectionChanged) + self.onSourceStackSelectionChanged([],[]) self.initActions() + def updateOutputs(self, *args): + + self.tableModelOutputImages.clearOutputs() + inputStacks = self.tableModelInputStacks.mStackImages + self.tableModelOutputImages.setMultiStackSources(inputStacks) + + def initActions(self): self.actionAddSourceStack.triggered.connect(self.onAddSource) @@ -230,10 +644,17 @@ class StackedBandInputDialog(QDialog, loadUI('stackedinputdatadialog.ui')): self.tableModelInputStacks.insertSources(paths) def onRemoveSources(self, *args): - pass + + model = self.tableViewSourceStacks.selectionModel() + assert isinstance(model, QItemSelectionModel) + + infos = [self.tableModelInputStacks.index2info(idx) for idx in model.selectedRows()] + self.tableModelInputStacks.removeSources(infos) + def onSourceStackSelectionChanged(self, selected, deselected): - pass + self.actionRemoveSourceStack.setEnabled(len(selected) > 0) + diff --git a/timeseriesviewer/ui/stackedinputdatadialog.ui b/timeseriesviewer/ui/stackedinputdatadialog.ui index 4d82824559448e51ec66022c120bffb708abd1d0..789ce1155b3c6d3f9c8bce3aa2d1f8e61d5526db 100644 --- a/timeseriesviewer/ui/stackedinputdatadialog.ui +++ b/timeseriesviewer/ui/stackedinputdatadialog.ui @@ -108,77 +108,6 @@ <item> <widget class="QTableView" name="tableViewSourceStacks"/> </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <property name="topMargin"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Next Date</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="spNextDTG"> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>999999</number> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="sbNextBand"> - <property name="text"> - <string>Next Band</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="spinBox"> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>999999</number> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Time stamp from</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="cbDTGData"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>2</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> </layout> </widget> </item> @@ -200,33 +129,19 @@ <item> <layout class="QGridLayout" name="gridLayout_2"> <property name="topMargin"> - <number>20</number> + <number>0</number> </property> - <item row="3" column="0"> - <widget class="QRadioButton" name="radioButton"> - <property name="text"> - <string>Directory</string> - </property> - </widget> - </item> - <item row="2" column="0"> + <item row="2" column="1"> <widget class="QRadioButton" name="radioButton_2"> <property name="text"> - <string>In-Memory</string> + <string>in memory</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>Basename</string> - </property> - </widget> - </item> - <item row="2" column="1"> + <item row="2" column="3"> <widget class="QFrame" name="frameInMemoryPath"> <property name="frameShape"> <enum>QFrame::StyledPanel</enum> @@ -251,25 +166,40 @@ <number>0</number> </property> <item> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>/vsimem/</string> + <widget class="QgsFileWidget" name="fileWidgetOutputdir"> + <property name="dialogTitle"> + <string>Select output directory</string> </property> </widget> </item> - <item> - <widget class="QLineEdit" name="tbInMemoryPath"/> - </item> </layout> </widget> </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="lineEdit"/> + <item row="2" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QRadioButton" name="radioButton"> + <property name="text"> + <string>in directory</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Prefix</string> + </property> + </widget> </item> - <item row="3" column="1"> - <widget class="QgsFileWidget" name="fileWidgetOutputdir"> - <property name="dialogTitle"> - <string>Select output directory</string> + <item row="0" column="1" colspan="3"> + <widget class="QLineEdit" name="tbFilePrefix"> + <property name="text"> + <string>timestack</string> </property> </widget> </item>