diff --git a/pyglance/glance/data.py b/pyglance/glance/data.py index fe407d66b04665ecca99c809339d2b26150ea300..af8e40591da7b19375260b2c5ce994d5b6f1aabc 100644 --- a/pyglance/glance/data.py +++ b/pyglance/glance/data.py @@ -237,14 +237,36 @@ class DiffInfoObject (object) : (if both a value and percent are present, two epsilon tests will be done) """ - # Upcasts to be used in difference computation to avoid overflow. Currently only unsigned - # ints are upcast. - # FUTURE: handle uint64s as well (there is no int128, so might have to detect overflow) + POSITIVE_UPCASTS = { + np.uint8: np.int16, + np.uint16: np.int32, + np.uint32: np.int64, + np.uint64: np.float32, + } + + # Upcasts to be used in difference computation to avoid overflow. + # right now this is agressively upcasting the comparison data DATATYPE_UPCASTS = { - np.uint8: np.int16, - np.uint16: np.int32, - np.uint32: np.int64 - } + np.int8: np.int16, + np.int16: np.int32, + np.int32: np.int64, + np.int64: np.float32, + + #np.float32: np.float64, + #np.float64: np.float128, + } + # FUTURE: right now the actual range of the data isn't being considered when upcasting + # (Note: numpy.finfo and numpy.iinfo can be used to get more data on types) + TYPE_MAXIMUM = { + np.int16: 32767, + np.int32: 2147483647, + np.int64: 9223372036854775807, + + np.float32: 3.4028235e+38, + + np.float64: 1.7976931348623157e+308, + #np.float128: 1.189731495357231765e+4932, + } def __init__(self, aDataObject, bDataObject, epsilonValue=0.0, epsilonPercent=None) : @@ -278,9 +300,17 @@ class DiffInfoObject (object) : type_to_return = np.common_type(data1, data2) changed_type = True - # upcast the type if we need to + # make sure we're using a type that has negative values in it + if type_to_return in DiffInfoObject.POSITIVE_UPCASTS : + type_to_return = DiffInfoObject.POSITIVE_UPCASTS[type_to_return] + changed_type = True + + # upcast the type if we think we'll need more space for subtracting if type_to_return in DiffInfoObject.DATATYPE_UPCASTS : type_to_return = DiffInfoObject.DATATYPE_UPCASTS[type_to_return] + changed_type = True + + if changed_type : LOG.debug('To prevent overflow, difference data will be upcast from (' + str(data1.dtype) + '/' + str(data2.dtype) + ') to: ' + str(type_to_return)) diff --git a/pyglance/glance/io.py b/pyglance/glance/io.py index cbe5223c28ce60ca2759bef167bbbdc07f1ecf6c..55873ddf59245d1bc654dfdbced6471a61f98e3a 100644 --- a/pyglance/glance/io.py +++ b/pyglance/glance/io.py @@ -52,6 +52,13 @@ except ImportError: LOG.info('no adl_blob format handler available') adl_blob = None +try : + from osgeo import gdal + LOG.info('loading osgeo module for GeoTIFF data file access') +except : + LOG.info('no osgeo available for reading GeoTIFF data files') + gdal = None + UNITS_CONSTANT = "units" fillValConst1 = '_FillValue' @@ -916,6 +923,174 @@ class aeri(object): # handle the variety of file suffixes by building aliases to aeri class cxs = rnc = cxv = csv = spc = sum = uvs = aeri +class tiff (object): + """wrapper for to open GeoTIFF data sets for comparison + __call__ yields sequence of variable names + __getitem__ returns individual variables ready for slicing to numpy arrays + """ + + _tiff = None + + # if we are using meaningful names, we will translate between + # the band index numbers and these names (otherwise bands use generic names) + EXPECTED_BAND_NAME_KEY = { + 1: ["grayscale value"], + 3: ["red", "green", "blue"], + 4: ["red", "green", "blue", "alpha"], + } + SPECIAL_NAMES_TO_IDX = { + "grayscale value": 1, + "red": 1, + "green": 2, + "blue": 3, + "alpha": 4, + } + + def _get_generic_band_name (self, number) : + """get a generic band name for this number""" + + return ("band at index " + str(number)) + + def _get_band_index_from_name (self, name) : + """get an index for the band from a name + + name may be either a meaningful name from the list that shows + up in SPECIAL_NAMES_TO_IDX's keys or a generic name that was + generated by _get_generic_band_name + """ + + to_return = None + + if name in self.SPECIAL_NAMES_TO_IDX.keys() : + to_return = self.SPECIAL_NAMES_TO_IDX[name] + else : + to_return = int(name.split(' ')[-1]) + + return to_return + + def __init__(self, filename, allowWrite=False, useMeaningfulNames=True): + + if gdal is None: + LOG.error('gdal is not installed and is needed in order to read GeoTIFF files') + assert(gdal is not None) + + if allowWrite: + LOG.warn("Write access requested, but is not currently supported for GeoTIFF files. File will be opened read-only.") + + self._tiff = gdal.Open(filename) + self.niceNames = useMeaningfulNames + + def __call__(self): + "yield names of variables to be compared" + + # GeoTIFF files don't actually have named variables, so get something appropriate based on the numbering of bands + num_bands = self._tiff.RasterCount + + to_return = [ ] + if self.niceNames and (num_bands in self.EXPECTED_BAND_NAME_KEY.keys()) : + to_return = self.EXPECTED_BAND_NAME_KEY[num_bands][:] + else : + for bandNumber in range(1, num_bands + 1) : + to_return.append(self._get_generic_band_name(bandNumber)) + + return to_return + + # this returns a numpy array with a copy of the full, scaled + # data for this variable, if the data type must be changed to allow + # for scaling it will be (so the return type may not reflect the + # type found in the original file) + def __getitem__(self, name): + + LOG.debug("opening variable: " + name) + + # first figure out the index for this variable + var_index = self._get_band_index_from_name(name) + + # get the data out of the file + temp_band = self._tiff.GetRasterBand(var_index) + temp_data = temp_band.ReadAsArray() + + # there is no standard scaling procedure for GeoTIFFs, so skip that! + + return temp_data + + # TODO, this hasn't been supported in other file types + def close (self) : + # Dave couldn't find any explicit way to close it other + # than let the garbage collector take care of it + self._tiff = None + + def get_variable_object(self, name): + return None + + def missing_value(self, name): + return None + + def create_new_variable(self, variablename, missingvalue=None, data=None, variabletocopyattributesfrom=None): + """ + TODO, this is not yet supported for GeoTIFFs + + create a new variable with the given name + optionally set the missing value (fill value) and data to those given + + the created variable will be returned, or None if a variable could not + be created + """ + + LOG.warn ("GeoTIFF io class does not yet support writing. Unable to create new variable in GeoTIFF file.") + + return None + + def add_attribute_data_to_variable(self, variableName, newAttributeName, newAttributeValue) : + """ + TODO this is not yet supported for GeoTIFFs + + if the attribute exists for the given variable, set it to the new value + if the attribute does not exist for the given variable, create it and set it to the new value + """ + + LOG.warn ("GeoTIFF io class does not yet support writing. Unable to add attribute information to GeoTIFF file.") + + return + + def get_variable_attributes (self, variableName, caseInsensitive=True) : + """ + returns all the attributes associated with a variable name + """ + + # FUTURE, GeoTIFF files do have attributes, but this isn't hooked up yet + + return { } + + def get_attribute(self, variableName, attributeName, caseInsensitive=True) : + """ + returns the value of the attribute if it is available for this variable, or None + """ + + # FUTURE, GeoTIFF files do have attributes, but this isn't hooked up yet + + return None + + def get_global_attributes(self, caseInsensitive=True) : + """ + get a list of all the global attributes for this file or None + """ + + # FUTURE, GeoTIFF files do have attributes, but this isn't hooked up yet + + return { } + + def get_global_attribute(self, attributeName, caseInsensitive=True) : + """ + returns the value of a global attribute if it is available or None + """ + + # FUTURE, GeoTIFF files do have attributes, but this isn't hooked up yet + + return None + +# people also name tiff files with one f... +tif = tiff def _search_xml(pathname): xs = '.xml' diff --git a/pyglance/glance/load.py b/pyglance/glance/load.py index 034d37af541b50dd9bf0f382cb3a76a460897741..e35ba4167b76d368909523a0c8578bbec12ee12d 100644 --- a/pyglance/glance/load.py +++ b/pyglance/glance/load.py @@ -9,7 +9,7 @@ Copyright (c) 2012 University of Wisconsin SSEC. All rights reserved. """ import logging -from pycdf import CDFError +#from pycdf import CDFError import numpy import glance.delta as delta @@ -249,10 +249,11 @@ def load_variable_data(fileObject, variableNameInFile, else : try : variableData = numpy.array(fileObject[variableNameInFile]) if forceDType is None else numpy.array(fileObject[variableNameInFile], dtype=forceDType) - except CDFError : + except Exception, ex : + import traceback exceptionToRaise = ValueError('Unable to retrieve ' + variableNameInFile + ' data. The variable name ' + ' may not exist in this file or an error may have occured while attempting to' + - ' access the data. Details of file access error observed: ' + str(CDFError)) + ' access the data. Details of file access error observed: ' + str(ex)) # if we ended up with an exception, raise that now if exceptionToRaise is not None :