Source code for gsdview.gdalbackend.helpers

# -*- coding: utf-8 -*-

### Copyright (C) 2008-2012 Antonio Valentino <a_valentino@users.sf.net>

### This file is part of GSDView.

### GSDView 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.

### GSDView is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### GNU General Public License for more details.

### You should have received a copy of the GNU General Public License
### along with GSDView; if not, write to the Free Software
### Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.


'''Core GDAL backend functions and classes.'''


import os
import glob
import shutil
import tempfile

from osgeo import gdal

from qt import QtGui

from ..gdalbackend import modelitems
from ..gdalbackend import gdalsupport


__author__ = 'Antonio Valentino <a_valentino@users.sf.net>'
__date__ = '$Date$'
__revision__ = '$Revision$'


[docs]class GdalHelper(object): '''Basee helper class for running external GDAL tools. Helper classes provide a common set of functionality for running GDAL tools in separate processes. Task performed are: * tool setup * temporay files and diractories creation * temporay files and diractories cleanup * finalization actions ''' _PROGRESS_RANGE = (0, 100) def __init__(self, app, tool): super(GdalHelper, self).__init__() self.app = app self.tool = tool self.progressdialog = None self._tmpdir = None
[docs] def setup_progress_dialog(self, title=''): dialog = QtGui.QProgressDialog(self.app) dialog.setModal(True) if title: dialog.setLabelText(title) dialog.hide() self.progressdialog = dialog return dialog
@property
[docs] def controller(self): return self.app.controller
@property
[docs] def logger(self): return self.app.logger
@property
[docs] def gdalbackend(self): return self.app.pluginmanager.plugins['gdalbackend']
@staticmethod
[docs] def ovrfiles(dirname): files = [] for pattern in ('*.ovr', '*.aux'): files.extend(glob.glob(os.path.join(dirname, pattern))) return files
[docs] def setup_tmpdir(self, dataset): '''Create a temporary diran copy the virtual file into it.''' vrtdirname = os.path.dirname(dataset.vrtfilename) try: tmpdir = os.path.join(vrtdirname, 'tmp') os.mkdir(tmpdir) except OSError: tmpdir = tempfile.mkdtemp() shutil.copy(dataset.vrtfilename, tmpdir) return tmpdir
[docs] def cleanup(self): if self._tmpdir: shutil.rmtree(self._tmpdir) if os.path.exists(self._tmpdir): self.logger.warning('unable ro remove remporary dir: ' '"%s"' % self._tmpdir) self._tmpdir = None
[docs] def setProgressRange(self, minimum, maximum): self.app.progressbar.setRange(minimum, maximum) if self.progressdialog: self.progressdialog.setRange(minimum, maximum)
def _reset_progress(self): self.setProgressRange(0, 100) if self.progressdialog: self.progressdialog.hide()
[docs] def reset(self): self._reset_progress()
def _connect_signals(self): self.controller.finished.connect(self.finalize) if self.progressdialog: self.progressdialog.canceled.connect(self.controller.stop_tool) self.app.progressbar.valueChanged.connect( self.progressdialog.setValue) def _disconnect_signals(self): # @TODO: catch exceptions self.controller.finished.disconnect(self.finalize) if self.progressdialog: self.app.progressbar.valueChanged.disconnect( self.progressdialog.setValue) self.progressdialog.canceled.disconnect(self.controller.stop_tool)
[docs] def do_start(self, *args, **kwargs): raise NotImplementedError(self.__class__.__name__ + '.do_start')
[docs] def start(self, *args, **kwargs): if self.controller.isbusy: self.logger.warning('unable to perform overview computation: ' 'the subprocess controller is currently ' 'busy.') return else: self.logger.debug('run the "%s" subprocess.' % os.path.basename(self.tool.executable)) # @TODO: check: this instruuctin in this position don' seems to work # (the progressbar hangs) #self.setProgressRange(*self._PROGRESS_RANGE) #if self.progressdialog: # #self.progressdialog.reset() # self.progressdialog.show() # @TODO: connect signals after the process succefully started (??) self._connect_signals() try: startfailure = self.do_start(*args, **kwargs) except Exception as e: #self.logger.error(str(e), exc_info=True) self.logger.debug(str(e), exc_info=True) startfailure = True if startfailure: self._disconnect_signals() else: self.setProgressRange(*self._PROGRESS_RANGE) if self.progressdialog: #self.progressdialog.reset() self.progressdialog.show()
[docs] def do_finalize(self): pass
[docs] def do_finalize_on_error(self): pass #@QtCore.Slot() #@QtCore.Slot(int)
[docs] def finalize(self, returncode=0): try: self._disconnect_signals() # only call do_finalize if processing finished successfully if returncode == 0 and not self.controller.userstop: self.do_finalize() else: self.do_finalize_on_error() finally: self.cleanup() self._reset_progress()
[docs]class AddoHelper(GdalHelper): '''Helper class for gdaladdo execution on live datasets. In GSDView an external process running the gdaladdo is used to add oveviews to (virtual) dataset that are already open in GSDView itself. Unfortunately, as far as I know, there is no way to safely handle two dataset objects (pointing at the same vrt file) in two different processes. This helper class provides functions to perform overview computation on a private environment and then move the ovr/aux file back to the main cache folder of the dataset. After that the dataset is re-opened an all changes are safely reflected to the GUI. In case the overview computation is stopped before completion then the private gdaladdo environment is simply cleaned and no side effect arises. .. note:: if one wants to add overviews to a vrt dataset that already has overviews (i.e. the ovr/aux file already exists) then the ovr/aux file should be copyed in the private gdaladdo environment before starting computation. In this way the pre-existing overview are preserved but the copy operation could be heavy weight. Maybe some suggestion can be asked on the GDAL mailing-list. An alternative solution, the one currently implemented, is to force recomputation of all overview levels (exixting ones and newly selected) and then replace the ol overview file. This solution is not efficient since it doesn't re-use existing overviews but ensure no data loss in case the operation is stopped by the user. ''' def __init__(self, app, tool): super(AddoHelper, self).__init__(app, tool) self._datasetitem = None self._band = None
[docs] def target_levels(self, dataset): if self._band is not None: band = self._band else: band = dataset.GetRasterBand(1) oldlevels = [] estep = 3 threshold = 0.1 if band.GetOverviewCount(): oldlevels = gdalsupport.ovrLevels(band) if set(oldlevels).issuperset((2, 4)): estep = 2 threshold = 1.1 # @NOTE: use dataset for levels computation because the # IMAGE_STRUCTURE metadata are not propagated from # CachedDatasetItem to raster bands levels = gdalsupport.ovrComputeLevels(dataset, estep=estep, threshold=threshold) # @NOTE: the GDAL band info is configured to force recomputation of # all levels checked if levels: levels.extend(oldlevels) levels.sort() return levels
[docs] def do_start(self, item): #levels = gdalsupport.ovrComputeLevels(item) # @NOTE: use dataset for levels computation because the # IMAGE_STRUCTURE metadata are not propagated from # CachedDatasetItem to raster bands if not isinstance(item, modelitems.DatasetItem): # NOTE: a reguest of opening an overview is converted into a # request for opening the corresponding raster band while isinstance(item, modelitems.OverviewItem): item = item.parent() self._band = item dataset = item.parent() else: dataset = item assert isinstance(dataset, modelitems.CachedDatasetItem), str(dataset) levels = self.target_levels(dataset) # @NOTE: overviews are computed for all bands so I do this at # application level, before a specific band is choosen. # Maybe ths is not the best policy and overviews should be # computed only when needed instead if levels: self.logger.debug('requested levels: %s' % levels) # Run an external process for overviews computation self.app.statusBar().showMessage('Quick look image generation ...') self._tmpdir = self.setup_tmpdir(dataset) vrtfilename = os.path.basename(dataset.vrtfilename) vrtfilename = os.path.join(self._tmpdir, vrtfilename) self._datasetitem = dataset # use averaging in magphase space for complex raster bands if gdalsupport.has_complex_bands(dataset): self.tool.set_resampling_method('average_magphase') else: self.tool.set_resampling_method('average') args = [os.path.basename(vrtfilename)] args.extend(str(level) for level in levels) self.tool.cwd = os.path.dirname(vrtfilename) self.controller.run_tool(self.tool, *args) else: return True
[docs] def do_finalize(self): # @TODO: check if opening the dataset in update mode # (gdal.GA_Update) is a better solution dataset = self._datasetitem if not dataset: self.logger.debug('unable to retrieve dataset for finalization') return # move ovr files in the cache dir dirname = os.path.dirname(dataset.vrtfilename) ovrfiles = self.ovrfiles(self._tmpdir) for ovrfile in ovrfiles: dst = os.path.join(dirname, os.path.basename(ovrfile)) if os.path.exists(dst): os.remove(dst) shutil.move(ovrfile, dirname) dataset.reopen() for row in range(dataset.rowCount()): item = dataset.child(row) self.app.treeview.expand(item.index())
[docs] def reset(self): super(AddoHelper, self).reset() self._datasetitem = None self._band = None
[docs]class StatsHelper(GdalHelper): '''Helper class for statistics pre-computation on live raster bands.''' _PROGRESS_RANGE = (0, 0) # @TODO: test error control and user stop handling def __init__(self, app, tool): super(StatsHelper, self).__init__(app, tool) self._datasetitem = None self._banditem = None
[docs] def do_start(self, item): if not isinstance(item, modelitems.BandItem): raise ValueError('invalid band item: %s' % item) # NOTE: a reguest of opening an overview is converted into a request # for opening the corresponding raster band while isinstance(item, modelitems.OverviewItem): item = item.parent() dataset = item.parent() # Run an external process for statistics computation self.app.statusBar().showMessage('Compute coarse statistics ...') self._tmpdir = self.setup_tmpdir(dataset) vrtfilename = os.path.basename(dataset.vrtfilename) vrtfilename = os.path.join(self._tmpdir, vrtfilename) self._banditem = item self._datasetitem = dataset args = [os.path.basename(vrtfilename)] self.tool.cwd = os.path.dirname(vrtfilename) self.controller.run_tool(self.tool, *args)
[docs] def do_finalize(self): # @TODO: check if opening the dataset in update mode # (gdal.GA_Update) is a better solution dataset = self._datasetitem if not dataset: self.logger.debug('unable to retrieve dataset for finalization') return # set computed statisstic values bandno = self._banditem.GetBand() tmpvrt = os.path.join(self._tmpdir, os.path.basename(dataset.vrtfilename)) ds = gdal.Open(tmpvrt) if not ds: self.logger.warning('unable to open temporary virtual file for ' 'getting statistics.') return vrtband = ds.GetRasterBand(bandno) if not vrtband: self.logger.warning('unable to open raster band n. %d.' % bandno) return self.copy_data(vrtband) self.apply()
[docs] def reset(self): super(StatsHelper, self).reset() self._banditem = None self._datasetitem = None
[docs] def copy_data(self, vrtband): stats = gdalsupport.GetCachedStatistics(vrtband) if None in stats: self.logger.warning('unable to retrieve statistics.') return for name, value in zip(gdalsupport.GDAL_STATS_KEYS, stats): self._banditem.SetMetadataItem(name, str(value))
[docs] def apply(self): self.gdalbackend.newImageView(self._banditem)
[docs]class StatsDialogHelper(StatsHelper): '''Helper class for statistics computation on live raster bands.''' _PROGRESS_DIALOD_MSG = 'Statistics computation.' def __init__(self, app, tool): super(StatsDialogHelper, self).__init__(app, tool) self.dialog = None if self._PROGRESS_DIALOD_MSG: self.setup_progress_dialog(app.tr(self._PROGRESS_DIALOD_MSG))
[docs] def start(self, item, dialog=None): if dialog: self.dialog = dialog if not self.dialog: raise ValueError('"dialog" attribute not set') super(StatsDialogHelper, self).start(item)
[docs] def reset(self): super(StatsDialogHelper, self).reset() self.dialog = None
[docs] def apply(self): self.dialog.updateStatistics()
[docs]class HistDialogHelper(StatsDialogHelper): '''Helper class for histogram computation on live raster bands.''' _PROGRESS_RANGE = (0, 100) _PROGRESS_DIALOD_MSG = 'Histogram computation.'
[docs] def copy_data(self, vrtband): hmin, hmax, nbucketsm, hist = vrtband.GetDefaultHistogram() self._banditem.SetDefaultHistogram(hmin, hmax, hist)
[docs] def apply(self): hist = self._banditem.GetDefaultHistogram() #self.dialog.updateHistogram() self.dialog.setHistogram(*hist)
[docs]class AddoDialogHelper(AddoHelper): '''Helper class for overviews computation. .. seealso:: :class:`AddoHelper` ''' _PROGRESS_DIALOD_MSG = 'Overviews computation.' def __init__(self, app, tool): super(AddoDialogHelper, self).__init__(app, tool) self.dialog = None self.setup_progress_dialog(app.tr(self._PROGRESS_DIALOD_MSG))
[docs] def target_levels(self, dataset): return self.dialog.overviewWidget.levels()
[docs] def start(self, item, dialog=None): if dialog: self.dialog = dialog if not self.dialog: raise ValueError('"dialog" attribute not set') super(AddoDialogHelper, self).start(item)
[docs] def reset(self): super(AddoDialogHelper, self).reset() self.dialog = None
[docs] def do_finalize(self): super(AddoDialogHelper, self).do_finalize() self.dialog.updateOverviewInfo()

Get GSDView at SourceForge.net. Fast, secure and Free Open Source software downloads