def reflectance(self): """ Reflectance (0 .. 1) derived from DN and metadata, as numpy array """ if not self.meta: raise PygaarstRasterError( "Impossible to retrieve metadata for band. " + "No reflectance calculation possible.") if self.spacecraft == 'L8': self.gain = self.meta['RADIOMETRIC_RESCALING'][ 'REFLECTANCE_MULT_BAND_%s' % self.band] self.bias = self.meta['RADIOMETRIC_RESCALING'][ 'REFLECTANCE_ADD_BAND_%s' % self.band] sedeg = self.meta['IMAGE_ATTRIBUTES']['SUN_ELEVATION'] rawrad = ir.dn2rad(self.data, self.gain, self.bias) return rawrad / (np.sin(sedeg * np.pi / 180)) elif self.spacecraft in ['L5', 'L7']: if self.newmetaformat: sedeg = self.meta['IMAGE_ATTRIBUTES']['SUN_ELEVATION'] dac = self.meta['PRODUCT_METADATA']['DATE_ACQUIRED'] else: sedeg = self.meta['PRODUCT_PARAMETERS']['SUN_ELEVATION'] dac = self.meta['PRODUCT_METADATA']['ACQUISITION_DATE'] juliandac = int(datetime.date.strftime(dac, '%j')) d = lu.getd(juliandac) esun = lu.getesun(self.spacecraft, self.band) rad = self.radiance return (np.pi * d * d * rad) / (esun * np.sin(sedeg * np.pi / 180)) else: return None
def xy2ij(self, x, y, precise=False): """ Convert easting/northing coordinate pair(s) to array coordinate pairs(s). NOTE: see note at ij2xy() Arguments: x (float): scalar or array of easting coordinates y (float): scalar or array of northing coordinates precise (bool): if true, return fractional array coordinates Returns: i (int, or float): scalar or array of row coordinate index j (int, or float): scalar or array of column coordinate index """ if (rh._test_outside(x, self.easting[0], self.easting[-1]) or rh._test_outside(y, self.northing[0], self.northing[-1])): raise PygaarstRasterError("Coordinates out of bounds") i = (1 - (y - self.northing[0]) / (self.northing[-1] - self.northing[0])) * self.nrow j = ((x - self.easting[0]) / (self.easting[-1] - self.easting[0])) * self.ncol if precise: return i, j else: return int(np.floor(i)), int(np.floor(j))
def ij2xy(self, i, j): """ Converts array index pair(s) to easting/northing coordinate pairs(s). NOTE: array coordinate origin is in the top left corner whereas easting/northing origin is in the bottom left corner. Easting and northing are floating point numbers, and refer to the top-left corner coordinate of the pixel. i runs from 0 to nrow-1, j from 0 to ncol-1. For i=nrow and j=ncol, the bottom-right corner coordinate of the bottom-right pixel will be returned. This is identical to the bottom- right corner. Arguments: i (int): scalar or array of row coordinate index j (int): scalar or array of column coordinate index Returns: x (float): scalar or array of easting coordinates y (float): scalar or array of northing coordinates """ if (rh._test_outside(i, 0, self.nrow) or rh._test_outside(j, 0, self.ncol)): raise PygaarstRasterError("Coordinates %d, %d out of bounds" % (i, j)) x = self.easting[0] + j * self.delx y = self.northing[-1] + i * self.dely return x, y
def __getattr__(self, bandname): """ Override _gettattr__() for bandnames of the form bandN with N in the bands permissible for Ali. (See https://eo1.usgs.gov/sensors/hyperioncoverage). Warn if band is non-calibrated. Allows for infixing the filename just before the .TIF extension for pre-processed bands. """ # see https://eo1.usgs.gov/sensors/hyperioncoverage isband = False head, _, tail = bandname.lower().partition('band') try: band = tail.upper() if head == '': if band in self.permissiblebandid: isband = True else: raise PygaarstRasterError( "EO-1 ALI does not have a band %s. " + "Permissible band labels are between 1 and 10.") except ValueError: pass if isband: # Note: Landsat 7 has low and high gain bands 6, # with different label names keyname = "BAND%s_FILE_NAME" % band bandfn = self.meta['PRODUCT_METADATA'][keyname] base, ext = os.path.splitext(bandfn) postprocessfn = base + self.infix + ext bandpath = os.path.join(self.dirname, postprocessfn) self.bands[band] = ALIband(bandpath, band=band, scene=self) return self.bands[band] return object.__getattribute__(self, bandname)
def geodata(self): """Object representing the georeference data, in its entirety""" if self.geofilepath: geodat = h5py.File(self.geofilepath, "r") if not geodat: raise PygaarstRasterError( "Unable to open georeference file %s." % self.geofilepath) self.geogroupkey = geodat['All_Data'].keys()[0] return geodat['All_Data/%s' % self.geogroupkey] elif self.GEO: # It could be an aggregated multi-band VIIRS file # with embedded georeferences return self.GEO else: raise PygaarstRasterError( "Unable to find georeference information for %s." % self.filepath) return geodat
def radiance(self): """Radiance in W / um / m^2 / sr derived from digital number and metadata, as numpy array""" if not self.meta: raise PygaarstRasterError( "Impossible to retrieve metadata for band. " + "No radiance calculation possible.") self.gain = self.meta[ 'RADIANCE_SCALING']['BAND%s_SCALING_FACTOR' % self.band] self.bias = self.meta['RADIANCE_SCALING']['BAND%s_OFFSET' % self.band] return ir.dn2rad(self.data, self.gain, self.bias)
def geodata(self): """Object representing the georeference data, in its entirety""" geodat = None if self.geofilepath: # TODO pass else: raise PygaarstRasterError( "Unable to find georeference information for %s." % self.filepath) return geodat
def _latlonmetric(latarray, latref, lonarray, lonref): """Takes two numpy arrays of longitudes and latitudes and returns an array of the same shape of metrics representing distance for short distances""" if latarray.shape != latarray.shape: #arrays aren't the same shape raise PygaarstRasterError( "Latitude and longitude arrays have to be the same shape for " + "distance comparisons.") return np.sqrt( np.square(latarray - latref) + np.cos(np.radians(latarray)) * np.square(lonarray - lonref))
def reflectance(self): """ Reflectance (0 .. 1) derived from DN and metadata, as numpy array """ if not self.meta: raise PygaarstRasterError( "Impossible to retrieve metadata for band. " + "No reflectance calculation possible.") elif self.sensor == 'HYPERION': dac = dt.datetime.strptime( self.meta['PRODUCT_METADATA']['START_TIME'], '%Y %j %H:%M:%S') sedeg = self.meta['PRODUCT_PARAMETERS']['SUN_ELEVATION'] juliandac = int(dt.date.strftime(dac, '%j')) d = lu.getd(juliandac) esun = hyp.getesun(self.band) rad = self.radiance return ((np.pi * d * d * rad) / (esun * np.sin(sedeg * np.pi / 180))) else: raise PygaarstRasterError( "Unkown sensor {} on spacecraft {}.".format( self.sensor, self.spacecraft))
def __init__(self, dirname): self.dirname = dirname self.infix = '' metadata = mtl.parsemeta(dirname) try: self.meta = metadata['L1_METADATA_FILE'] except KeyError: raise PygaarstRasterError("Metadata from %s could not be read. " % dirname + " Please check your dataset.") self.spacecraft = _get_spacecraftid( self.meta['PRODUCT_METADATA']['SPACECRAFT_ID']) self.sensor = self.meta['PRODUCT_METADATA']['SENSOR_ID'] self.bands = {}
def __init__(self, filepath): try: #LOGGER.info("Opening %s" % filepath) self.dataobj = pyhdf.SD.SD(filepath) self.filepath = filepath self.dirname = os.path.dirname(filepath) self.rawmetadata = self.dataobj.attributes() except IOError as err: LOGGER.error("Could not open %s: %s" % (filepath, err.message)) raise if not self.dataobj: raise PygaarstRasterError( "Could not read data from %s as HDF4 file." % filepath)
def radiance(self): """Radiance in W / um / m^2 / sr derived from digital number and metadata, as numpy array""" if not self.meta: raise PygaarstRasterError( "Impossible to retrieve metadata " + "for band. No radiance calculation possible.") if int(self.band) <= 70: rad = self.data / self.meta['RADIANCE_SCALING'][ 'SCALING_FACTOR_VNIR'] else: rad = self.data / self.meta['RADIANCE_SCALING'][ 'SCALING_FACTOR_SWIR'] return rad.astype('float32')
def __init__(self, filepath): try: self.filepath = filepath self.dirname = os.path.dirname(filepath) # We'll want, possibly, metadata from the user block, for which # the HDF5 obj needs to be closed, but we first need the # userblock length. Thus some odd rigamarole... self.dataobj = h5py.File(filepath, "r") if self.dataobj.userblock_size != 0: self.userblock_size = self.dataobj.userblock_size self.dataobj.close() with open(self.filepath, 'rb') as source: self.userblock = source.read(self.userblock_size) self.dataobj = h5py.File(filepath, "r") except IOError as err: LOGGER.error("Could not open %s: %s" % (filepath, err.message)) raise if not self.dataobj: raise PygaarstRasterError( "Could not read data from %s as HDF5 file." % filepath)
def tKelvin(self): """Radiant (brightness) temperature at the sensor in K, implemented for Landsat thermal infrared bands.""" if not self.scene: raise PygaarstRasterError( "Impossible to retrieve metadata for band. " + "No radiance calculation possible.") if ((self.spacecraft == 'L8' and self.band not in ['10', '11']) or (self.spacecraft != 'L8' and not self.band.startswith('6'))): LOGGER.warning("Automatic brightness Temp not implemented. " + "Cannot calculate temperature. Sorry.") return None elif self.spacecraft == 'L8': self.k1 = self.meta['TIRS_THERMAL_CONSTANTS']['K1_CONSTANT_BAND_%s' % self.band] self.k2 = self.meta['TIRS_THERMAL_CONSTANTS']['K2_CONSTANT_BAND_%s' % self.band] elif self.spacecraft in ['L4', 'L5', 'L7']: self.k1, self.k2 = lu.getKconstants(self.spacecraft) return ir.rad2kelvin(self.radiance, self.k1, self.k2)
def __getattr__(self, bandname): """ Overrides _gettattr__() for bandnames bandN with N in l.LANDSATBANDS. Allows for infixing the filename just before the .TIF extension for pre-processed bands. """ isband = False head, sep, tail = bandname.lower().partition('band') try: band = tail.upper() if head == '': if band in self.permissiblebandid: isband = True else: raise PygaarstRasterError( "Spacecraft %s " % self.spacecraft + "does not have a band %s. " % band + "Permissible band labels are %s." % ', '.join(self.permissiblebandid)) except ValueError: pass if isband: # Note: Landsat 7 has low and high gain bands 6, # with different label names if self.newmetaformat: bandstr = band.replace('L', '_VCID_1').replace('H', '_VCID_2') keyname = "FILE_NAME_BAND_%s" % bandstr else: bandstr = band.replace('L', '1').replace('H', '2') keyname = "BAND%s_FILE_NAME" % bandstr bandfn = self.meta['PRODUCT_METADATA'][keyname] base, ext = os.path.splitext(bandfn) postprocessfn = base + self.infix + ext bandpath = os.path.join(self.dirname, postprocessfn) self.bands[band] = Landsatband(bandpath, band=band, scene=self) return self.bands[band] else: return object.__getattribute__(self, bandname)
def __getattr__(self, bandname): """ Override _gettattr__() for bandnames bandN with N in the bands permissible for Hyperion (See https://eo1.usgs.gov/sensors/hyperioncoverage). Warn if band is a non-calibrated one. Allows for infixing the filename just before the .TIF extension for pre-processed bands. """ # see https://eo1.usgs.gov/sensors/hyperioncoverage isband = False head, sep, tail = bandname.lower().partition('band') try: band = tail.upper() if head == '': if band in self.permissiblebandid: isband = True if band not in self.calibratedbandid: LOGGER.warning('Hyperion band %s is not calibrated.' % band) else: raise PygaarstRasterError( "EO-1 Hyperion does not have a band %s. " % band + "Permissible band labels are between 1 and 242.") except ValueError: pass if isband: keyname = "BAND%s_FILE_NAME" % band bandfn = self.meta['PRODUCT_METADATA'][keyname] base, ext = os.path.splitext(bandfn) postprocessfn = base + self.infix + ext bandpath = os.path.join(self.dirname, postprocessfn) self.bands[band] = Hyperionband(bandpath, band=band, scene=self) return self.bands[band] else: return object.__getattribute__(self, bandname)
def radiance(self): """ Radiance in W/um/m^2/sr derived from DN and metadata, as numpy array """ if not self.meta: raise PygaarstRasterError( "Impossible to retrieve metadata for band. " + "No radiance calculation possible.") if self.spacecraft == 'L8': self.gain = self.meta['RADIOMETRIC_RESCALING'][ 'RADIANCE_MULT_BAND_%s' % self.band] self.bias = self.meta['RADIOMETRIC_RESCALING'][ 'RADIANCE_ADD_BAND_%s' % self.band] return ir.dn2rad(self.data, self.gain, self.bias) elif self.newmetaformat: bandstr = self.band.replace('L', '_VCID_1').replace('H', '_VCID_2') lmax = self.meta['MIN_MAX_RADIANCE']['RADIANCE_MAXIMUM_BAND_%s' % bandstr] lmin = self.meta['MIN_MAX_RADIANCE']['RADIANCE_MINIMUM_BAND_%s' % bandstr] qcalmax = self.meta['MIN_MAX_PIXEL_VALUE'][ 'QUANTIZE_CAL_MAX_BAND_%s' % bandstr] qcalmin = self.meta['MIN_MAX_PIXEL_VALUE'][ 'QUANTIZE_CAL_MIN_BAND_%s' % bandstr] gain, bias = ir.gainbias(lmax, lmin, qcalmax, qcalmin) return ir.dn2rad(self.data, gain, bias) else: bandstr = self.band.replace('L', '1').replace('H', '2') lmax = self.meta['MIN_MAX_RADIANCE']['LMAX_BAND%s' % bandstr] lmin = self.meta['MIN_MAX_RADIANCE']['LMIN_BAND%s' % bandstr] qcalmax = self.meta['MIN_MAX_PIXEL_VALUE']['QCALMAX_BAND%s' % bandstr] qcalmin = self.meta['MIN_MAX_PIXEL_VALUE']['QCALMIN_BAND%s' % bandstr] gain, bias = ir.gainbias(lmax, lmin, qcalmax, qcalmin) return ir.dn2rad(self.data, gain, bias) return None
def spectrum(self, i_idx, j_idx, bands='calibrated', bdsel=[]): """ Calculates the radiance spectrum for one pixel. Arguments: i_idx (int): first coordinate index of the pixel j_idx (int): second coordinate index of the pixel bands (str): indicates the bands that are used 'calibrated' (default): only use calibrated bands 'high': use uncalibrated bands 225-242 'low': use uncalibrated bands 1-7 'all': use all available bands 'selected': use bdsel attribute or argument bdsel: sequence data type containing band indices to select """ rads = [] if bands == 'calibrated': bnd = self.hyperionbands[self.band_is_calibrated] elif bands == 'selected': bnd = self.hyperionbands[bdsel] if bnd.size == 0 and self.bandselection: bnd = self.hyperionbands[self.bandselection] elif bands == 'all': bnd = self.hyperionbands elif bands == 'high': bnd = self.hyperionbands[224:] elif bands == 'low': bnd = self.hyperionbands[:7] else: raise PygaarstRasterError("Unrecognized argument %s for bands " % bands + "in raser.HyperionScene.") for band in bnd: rad = self.__getattr__(band).radiance rads.append(rad[i_idx, j_idx]) del rad return rads
def clone(self, newpath, newdata): """ Creates new GeoTIFF object from existing: new data, same georeference. Arguments: newpath: valid file path newdata: numpy array, 2 or 3-dim Returns: A raster.GeoTIFF object """ # convert Numpy dtype objects to GDAL type codes # see https://gist.github.com/chryss/8366492 NPDTYPE2GDALTYPECODE = { "uint8": 1, "int8": 1, "uint16": 2, "int16": 3, "uint32": 4, "int32": 5, "float32": 6, "float64": 7, "complex64": 10, "complex128": 11, } # check if newpath is potentially a valid file path to save data dirname, fname = os.path.split(newpath) if dirname: if not os.path.isdir(dirname): raise PygaarstRasterError( "%s is not a valid directory to save file to " % dirname) if os.path.isdir(newpath): LOGGER.warning("%s is a directory." % dirname + " Choose a name " + "that is suitable for writing a dataset to.") if (newdata.shape != self.data.shape and newdata.shape != self.data[0, ...].shape): raise PygaarstRasterError( "New and cloned GeoTIFF dataset must be the same shape.") dims = newdata.ndim if dims == 2: bands = 1 elif dims > 2: bands = newdata.shape[0] else: raise PygaarstRasterError( "New data array has only %s dimensions." % dims) try: LOGGER.info(newdata.dtype.name) LOGGER.info(NPDTYPE2GDALTYPECODE) LOGGER.info(NPDTYPE2GDALTYPECODE[newdata.dtype.name]) gdaltype = NPDTYPE2GDALTYPECODE[newdata.dtype.name] except KeyError as err: raise PygaarstRasterError( "Data type in array %s " % newdata.dtype.name + "cannot be converted to GDAL data type: \n%s" % err.message) proj = self.projection geotrans = self._gtr gtiffdr = gdal.GetDriverByName('GTiff') gtiff = gtiffdr.Create(newpath, self.ncol, self.nrow, bands, gdaltype) gtiff.SetProjection(proj) gtiff.SetGeoTransform(geotrans) if dims == 2: gtiff.GetRasterBand(1).WriteArray(newdata) else: for idx in range(dims): gtiff.GetRasterBand(idx + 1).WriteArray(newdata[idx, :, :]) gtiff = None return GeoTIFF(newpath)