def _calculateResidual(self, downscaledScene, originalScene, originalSceneQuality=None): ''' Private function. Calculates residual between overlapping high-resolution and low-resolution images. ''' # First subset and reproject original (low res) scene to fit with # downscaled (high res) scene subsetScene_LR = utils.reprojectSubsetLowResScene( downscaledScene, originalScene, resampleAlg=gdal.GRA_NearestNeighbour) data_LR = subsetScene_LR.GetRasterBand(1).ReadAsArray().astype(float) gt_LR = subsetScene_LR.GetGeoTransform() # If quality file for the low res scene is provided then mask out all # bad quality pixels in the subsetted LR scene. Otherwise assume that all # low res pixels are of good quality. if originalSceneQuality is not None: subsetQuality_LR = utils.reprojectSubsetLowResScene( downscaledScene, originalSceneQuality, resampleAlg=gdal.GRA_NearestNeighbour) goodPixMask_LR = subsetQuality_LR.GetRasterBand(1).ReadAsArray() goodPixMask_LR = np.in1d(goodPixMask_LR.ravel(), self.lowResGoodQualityFlags).reshape( goodPixMask_LR.shape) data_LR[~goodPixMask_LR] = np.nan # Then resample high res scene to low res pixel size if self.disaggregatingTemperature: # When working with tempratures they should be converted to # radiance values before aggregating to be physically accurate. radianceScene = utils.saveImg( downscaledScene.GetRasterBand(1).ReadAsArray()**4, downscaledScene.GetGeoTransform(), downscaledScene.GetProjection(), "MEM", noDataValue=np.nan) resMean, _ = utils.resampleHighResToLowRes(radianceScene, subsetScene_LR) # Find the residual (difference) between the two) residual_LR = data_LR**4 - resMean[:, :, 0] else: resMean, _ = utils.resampleHighResToLowRes(downscaledScene, subsetScene_LR) # Find the residual (difference) between the two residual_LR = data_LR - resMean[:, :, 0] # Smooth the residual and resample to high resolution residual = utils.binomialSmoother(residual_LR) residualDs = utils.saveImg(residual, subsetScene_LR.GetGeoTransform(), subsetScene_LR.GetProjection(), "MEM", noDataValue=np.nan) residualScene_BL = utils.resampleWithGdalWarp(residualDs, downscaledScene, resampleAlg="bilinear") residualDs = None residual = residualScene_BL.GetRasterBand(1).ReadAsArray() # Sometimes there can be 1 HR pixel NaN border arond LR invalid pixels due to resampling. # Fuction below fixes this. Image border pixels are excluded due to numba stencil # limitations. residual[1:-1, 1:-1] = utils.removeEdgeNaNs(residual)[1:-1, 1:-1] residualScene_BL = None # The residual array might be slightly smaller then the downscaled because # of the subsetting of the low resolution scene. In that case just pad # the missing values with neighbours. downscaled = downscaledScene.GetRasterBand(1).ReadAsArray() if downscaled.shape != residual.shape: temp = np.zeros(downscaled.shape) temp[:residual.shape[0], :residual.shape[1]] = residual temp[residual.shape[0]:, :] = \ temp[2*(residual.shape[0] - downscaled.shape[0]):residual.shape[0] - downscaled.shape[0], :] temp[:, residual.shape[1]:] = \ temp[:, 2*(residual.shape[1] - downscaled.shape[1]):residual.shape[1] - downscaled.shape[1]] residual = temp residualScene = None subsetScene_LR = None subsetQuality_LR = None return residual, residual_LR, gt_LR
def residualAnalysis(self, disaggregatedFile, lowResFilename, lowResQualityFilename=None, doCorrection=True): ''' Perform residual analysis and (optional) correction on the disaggregated file (see [Gao2012] 2.4). Parameters ---------- disaggregatedFile: string or GDAL file object If string, path to the disaggregated image file; if gdal file object, the disaggregated image. lowResFilename: string Path to the low-resolution image file corresponding to the high-resolution disaggregated image. lowResQualityFilename: string (optional, default: None) Path to low-resolution quality image file. If provided then low quality values are masked out during residual analysis. Otherwise all values are considered to be of good quality. doCorrection: boolean (optional, default: True) Flag indication whether residual (bias) correction should be performed or not. Returns ------- residualImage: GDAL memory file object The file object contains an in-memory, georeferenced residual image. correctedImage: GDAL memory file object The file object contains an in-memory, georeferenced residual corrected disaggregated image, or None if doCorrection was set to False. ''' if not os.path.isfile(str(disaggregatedFile)): scene_HR = disaggregatedFile else: scene_HR = gdal.Open(disaggregatedFile) scene_LR = gdal.Open(lowResFilename) if lowResQualityFilename is not None: quality_LR = gdal.Open(lowResQualityFilename) else: quality_LR = None residual_HR, residual_LR, gt_res = self._calculateResidual( scene_HR, scene_LR, quality_LR) if self.disaggregatingTemperature: if doCorrection: corrected = (residual_HR + scene_HR.GetRasterBand(1).ReadAsArray()**4)**0.25 correctedImage = utils.saveImg(corrected, scene_HR.GetGeoTransform(), scene_HR.GetProjection(), "MEM", noDataValue=np.nan) else: correctedImage = None # Convert residual back to temperature for easier visualisation residual_LR = (residual_LR + 273.15**4)**0.25 - 273.15 else: if doCorrection: corrected = residual_HR + scene_HR.GetRasterBand( 1).ReadAsArray() correctedImage = utils.saveImg(corrected, scene_HR.GetGeoTransform(), scene_HR.GetProjection(), "MEM", noDataValue=np.nan) else: correctedImage = None residualImage = utils.saveImg(residual_LR, gt_res, scene_HR.GetProjection(), "MEM", noDataValue=np.nan) print("LR residual bias: " + str(np.nanmean(residual_LR))) print("LR residual RMSD: " + str(np.nanmean(residual_LR**2)**0.5)) scene_HR = None scene_LR = None quality_LR = None return residualImage, correctedImage
print("Training regressor...") disaggregator.trainSharpener() print("Sharpening...") downscaledFile = disaggregator.applySharpener(highResFilename, lowResFilename) print("Residual analysis...") residualImage, correctedImage = disaggregator.residualAnalysis( downscaledFile, lowResFilename, lowResMaskFilename, doCorrection=True) print("Saving output...") highResFile = gdal.Open(highResFilename) if correctedImage is not None: outImage = correctedImage else: outImage = downscaledFile # outData = utils.binomialSmoother(outData) outFile = utils.saveImg( outImage.GetRasterBand(1).ReadAsArray(), outImage.GetGeoTransform(), outImage.GetProjection(), outputFilename) residualFile = utils.saveImg( residualImage.GetRasterBand(1).ReadAsArray(), residualImage.GetGeoTransform(), residualImage.GetProjection(), os.path.splitext(outputFilename)[0] + "_residual" + os.path.splitext(outputFilename)[1]) outFile = None residualFile = None downsaceldFile = None highResFile = None print(time.time() - start_time, "seconds")
def applySharpener(self, highResFilename, lowResFilename=None): ''' Apply the trained sharpener to a given high-resolution image to derive corresponding disaggregated low-resolution image. If local regressions were used during training then they will only be applied where their moving window extent overlaps with the high resolution image passed to this function. Global regression will be applied to the whole high-resolution image wihtout geographic constraints. Parameters ---------- highResFilename: string Path to the high-resolution image file do be used during disaggregation. lowResFilename: string (optional, default: None) Path to the low-resolution image file corresponding to the high-resolution input file. If local regressions were trained and low-resolution filename is given then the local and global regressions will be combined based on residual values of the different regressions to the low-resolution image (see [Gao2012] 2.3). If local regressions were trained and low-resolution filename is not given then only the local regressions will be used. Returns ------- outImage: GDAL memory file object The file object contains an in-memory, georeferenced disaggregator output. ''' # Open and read the high resolution input file highResFile = gdal.Open(highResFilename) inData = np.zeros((highResFile.RasterYSize, highResFile.RasterXSize, highResFile.RasterCount)) for band in range(highResFile.RasterCount): data = highResFile.GetRasterBand(band + 1).ReadAsArray().astype(float) no_data = highResFile.GetRasterBand(band + 1).GetNoDataValue() data[data == no_data] = np.nan inData[:, :, band] = data gt = highResFile.GetGeoTransform() shape = inData.shape ysize = shape[0] xsize = shape[1] # Temporarly get rid of NaN's nanInd = np.isnan(inData) inData[nanInd] = 0 outWindowData = np.empty((ysize, xsize)) * np.nan # Do the downscailing on the moving windows if there are any for i, extent in enumerate(self.windowExtents): if self.reg[i] is not None: [minX, minY] = utils.point2pix(extent[0], gt) # UL [minX, minY] = [max(minX, 0), max(minY, 0)] [maxX, maxY] = utils.point2pix(extent[1], gt) # LR [maxX, maxY] = [min(maxX, xsize), min(maxY, ysize)] windowInData = inData[minY:maxY, minX:maxX, :] outWindowData[minY:maxY, minX:maxX] = \ self._doPredict(windowInData, self.reg[i]) # Do the downscailing on the whole input image if self.reg[-1] is not None: outFullData = self._doPredict(inData, self.reg[-1]) else: outFullData = np.empty((ysize, xsize)) * np.nan # Combine the windowed and whole image regressions # If there is no windowed regression just use the whole image regression if np.all(np.isnan(outWindowData)): outData = outFullData # If corresponding low resolution file is provided then combine the two # regressions based on residuals (see section 2.3 of Gao paper) elif lowResFilename is not None: lowResScene = gdal.Open(lowResFilename) outWindowScene = utils.saveImg(outWindowData, highResFile.GetGeoTransform(), highResFile.GetProjection(), "MEM", noDataValue=np.nan) windowedResidual, _, _ = self._calculateResidual( outWindowScene, lowResScene) outWindowScene = None outFullScene = utils.saveImg(outFullData, highResFile.GetGeoTransform(), highResFile.GetProjection(), "MEM", noDataValue=np.nan) fullResidual, _, _ = self._calculateResidual( outFullScene, lowResScene) outFullScene = None lowResScene = None # windowed weight ww = (1 / windowedResidual)**2 / ((1 / windowedResidual)**2 + (1 / fullResidual)**2) # full weight fw = 1 - ww outData = outWindowData * ww + outFullData * fw # Otherwised use just windowed regression else: outData = outWindowData # Fix NaN's nanInd = np.any(nanInd, -1) outData[nanInd] = np.nan outImage = utils.saveImg(outData, highResFile.GetGeoTransform(), highResFile.GetProjection(), "MEM", noDataValue=np.nan) highResFile = None inData = None return outImage
def save_image(data, geotransform, projection, filename): return saveImg(data, geotransform, projection, filename)