Source code for tuiview.viewerstretch


"""
Module that contains ViewerStretch and StretchRule classes
"""
# This file is part of 'TuiView' - a simple Raster viewer
# Copyright (C) 2012  Sam Gillingham
#
# 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.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

import copy
import json
from osgeo import gdal
from . import viewererrors

gdal.UseExceptions()

# constants for specifying how to display an image 
VIEWER_MODE_DEFAULT = 0
VIEWER_MODE_COLORTABLE = 1
VIEWER_MODE_GREYSCALE = 2
VIEWER_MODE_RGB = 3
VIEWER_MODE_PSEUDOCOLOR = 4

# how to stretch an image
VIEWER_STRETCHMODE_DEFAULT = 0
VIEWER_STRETCHMODE_NONE = 1  # color table, or pre stretched data
VIEWER_STRETCHMODE_LINEAR = 2
VIEWER_STRETCHMODE_STDDEV = 3
VIEWER_STRETCHMODE_HIST = 4

# for storing a stretch within a file
VIEWER_STRETCH_METADATA_KEY = 'VIEWER_STRETCH'

# default stretchparams
VIEWER_DEFAULT_STDDEV = 2.0
VIEWER_DEFAULT_HISTMIN = 0.025
VIEWER_DEFAULT_HISTMAX = 0.01

VIEWER_DEFAULT_NOCOLOR = (0, 0, 0, 0)


[docs]class ViewerStretch(object): """ Class that represents the stretch. Use the methods here to set the type of stretch you want and the bands used """ def __init__(self): self.mode = VIEWER_MODE_DEFAULT self.stretchmode = VIEWER_STRETCHMODE_DEFAULT self.stretchparam = None self.bands = None self.rampName = None # from colorbrewer2.org self.nodata_rgba = VIEWER_DEFAULT_NOCOLOR self.background_rgba = VIEWER_DEFAULT_NOCOLOR self.nan_rgba = VIEWER_DEFAULT_NOCOLOR self.attributeTableSize = None # override with size of attribute table # if one exists # LUT will then be created with this size self.readLUTFromText = None # if not None, path to text # file to read LUT out of self.readLUTFromGDAL = None # if not None, path to GDAL dataset # to read LUT out of
[docs] def setBands(self, bands): "Set the bands to use. bands should be a tuple of 1-based ints" self.bands = bands
[docs] def setColorTable(self): "Use the color table in the image" self.mode = VIEWER_MODE_COLORTABLE self.stretchmode = VIEWER_STRETCHMODE_NONE
[docs] def setGreyScale(self): "Display a single band in greyscale" self.mode = VIEWER_MODE_GREYSCALE
[docs] def setPseudoColor(self, rampName): "Display with given color ramp" self.mode = VIEWER_MODE_PSEUDOCOLOR self.rampName = rampName
[docs] def setRGB(self): "Display 3 bands as RGB" self.mode = VIEWER_MODE_RGB
[docs] def setNoStretch(self): "Don't do a stretch - data is already stretched" self.stretchmode = VIEWER_STRETCHMODE_NONE
[docs] def setLinearStretch(self, minVal=None, maxVal=None): """ Just stretch linearly between min and max values if None, range of the data used """ self.stretchmode = VIEWER_STRETCHMODE_LINEAR self.stretchparam = (minVal, maxVal)
[docs] def setStdDevStretch(self, stddev=VIEWER_DEFAULT_STDDEV): "Do a standard deviation stretch" self.stretchmode = VIEWER_STRETCHMODE_STDDEV self.stretchparam = (stddev,)
[docs] def setHistStretch(self, minVal=VIEWER_DEFAULT_HISTMIN, maxVal=VIEWER_DEFAULT_HISTMAX): "Do a histogram stretch" self.stretchmode = VIEWER_STRETCHMODE_HIST self.stretchparam = (minVal, maxVal)
[docs] def setNoDataRGBA(self, rgba): "Set the RGBA to display No Data values as" self.nodata_rgba = rgba
[docs] def setBackgroundRGBA(self, rgba): "Set the RGB to display Background areas as" self.background_rgba = rgba
[docs] def setNaNRGBA(self, rgba): "Set the RGB to display NaN areas as" self.nan_rgba = rgba
[docs] def setAttributeTableSize(self, size): """ set with size of attribute table if one exists LUT will then be created with this size set to None for default behaviour """ self.attributeTableSize = size
[docs] def setLUTFromText(self, fname): "Read in the LUT from specified text file" self.readLUTFromText = fname
[docs] def setLUTFromGDAL(self, fname): "Read LUT from specified GDAL dataset" self.readLUTFromGDAL = fname
[docs] def toString(self): """ Convert to a JSON encoded string """ rep = {'mode': self.mode, 'stretchmode': self.stretchmode, 'stretchparam': self.stretchparam, 'bands': self.bands, 'nodata_rgba': self.nodata_rgba, 'rampname': self.rampName, 'background_rgba': self.background_rgba, 'nan_rgba': self.nan_rgba} return json.dumps(rep)
[docs] @staticmethod def fromTextFileWithLUT(fname): """ For reading a .stretch file. The stretch is read out of the first line and setLUTFromText called with this file also since .stretch files contain both """ fileobj = open(fname) s = fileobj.readline() fileobj.close() stretch = ViewerStretch.fromString(s) stretch.setLUTFromText(fname) return stretch
[docs] @staticmethod def fromGDALFileWithLUT(fname): """ For reading a GDAL file with stretch and LUT saved. The stretch is read out and setLUTFromGDAL called with this file also """ gdaldataset = gdal.Open(fname) stretch = ViewerStretch.readFromGDAL(gdaldataset) del gdaldataset if stretch is not None: stretch.setLUTFromGDAL(fname) return stretch
[docs] @staticmethod def fromString(string): """ Create a ViewerStretch instance from a json encoded string created by toString() """ rep = json.loads(str(string)) obj = ViewerStretch() obj.mode = rep['mode'] obj.stretchmode = rep['stretchmode'] obj.stretchparam = rep['stretchparam'] obj.bands = rep['bands'] if 'nodata_rgba' in rep: obj.nodata_rgba = rep['nodata_rgba'] if 'background_rgba' in rep: obj.background_rgba = rep['background_rgba'] if 'nan_rgba' in rep: obj.nan_rgba = rep['nan_rgba'] if 'rampname' in rep: obj.rampName = rep['rampname'] return obj
[docs] def writeToGDAL(self, gdaldataset): """ Write this stretch into the GDAL file assumed the dataset opened with GA_Update Good idea to reopen any other handles to dataset """ string = self.toString() gdaldataset.SetMetadataItem(VIEWER_STRETCH_METADATA_KEY, string)
[docs] @staticmethod def deleteFromGDAL(gdaldataset): """ Remove the stretch entry from this dataset assumed the dataset opened with GA_Update """ # can't seem to delete an item so set to empty string # we test for this explicity below gdaldataset.SetMetadataItem(VIEWER_STRETCH_METADATA_KEY, '')
[docs] @staticmethod def readFromGDAL(gdaldataset): """ See if there is an entry in the GDAL metadata, and return a ViewerStretch instance, otherwise None """ obj = None string = gdaldataset.GetMetadataItem(VIEWER_STRETCH_METADATA_KEY) if string is not None and string != '': obj = ViewerStretch.fromString(string) return obj
# Comparison constants to use in StretchRule VIEWER_COMP_LT = 0 # Less than VIEWER_COMP_GT = 1 # greater than VIEWER_COMP_EQ = 2 # equal
[docs]class StretchRule(object): """ Class that represents a 'rule' and a stretch to be applied when that rule matches. The rule contains information about number of bands in a dataset and how to compare, plus if a dataset has a colour table in a particular band """ def __init__(self, comp, value, ctband, stretch): self.comp = comp self.value = value self.ctband = ctband # or None self.stretch = copy.copy(stretch) # can reuse stretch object for something else
[docs] def isMatch(self, gdaldataset): """ Does this rule match the given dataset? """ match = False # check band numbers if self.comp == VIEWER_COMP_LT: match = gdaldataset.RasterCount < self.value elif self.comp == VIEWER_COMP_GT: match = gdaldataset.RasterCount > self.value elif self.comp == VIEWER_COMP_EQ: match = gdaldataset.RasterCount == self.value else: msg = 'invalid value for comparison' raise viewererrors.InvalidParameters(msg) if (match and self.ctband is not None and self.ctband <= gdaldataset.RasterCount): # we match the number of bands # but we need to check there is a color # table in the specified band gdalband = gdaldataset.GetRasterBand(self.ctband) layerType = gdalband.GetMetadataItem('LAYER_TYPE') match = False # only check if file is thematic anyway if layerType is not None and layerType == 'thematic': # we really need to check only that the RAT # reports that we have the right columns hasRed = False hasGreen = False hasBlue = False hasAlpha = False rat = gdalband.GetDefaultRAT() if rat is not None: ncols = rat.GetColumnCount() for col in range(ncols): usage = rat.GetUsageOfCol(col) if usage == gdal.GFU_Red: hasRed = True elif usage == gdal.GFU_Green: hasGreen = True elif usage == gdal.GFU_Blue: hasBlue = True elif usage == gdal.GFU_Alpha: hasAlpha = True match = hasRed and hasGreen and hasBlue and hasAlpha return match
[docs] def toString(self): """ Convert to a JSON encoded string """ rep = {'comp': self.comp, 'value': self.value, 'ctband': self.ctband, 'stretch': self.stretch.toString()} return json.dumps(rep)
[docs] @staticmethod def fromString(string): """ Create a StretchRule instance from a json encoded string created by toString() """ rep = json.loads(str(string)) stretch = ViewerStretch.fromString(rep['stretch']) obj = StretchRule(rep['comp'], rep['value'], rep['ctband'], stretch) return obj