def _estimateRegistration(self, baseImage, otherImage, outputPath): '''Writes an estimated registration transform to a file based on geo metadata.''' # This function assumes the images are in the same projection system! # Get the projection bounds and size in both images baseGeoInfo = IrgGeoFunctions.getImageGeoInfo(baseImage, False) otherGeoInfo = IrgGeoFunctions.getImageGeoInfo(otherImage, False) baseProjBounds = baseGeoInfo[ 'projection_bounds'] otherProjBounds = otherGeoInfo['projection_bounds'] baseImageSize = baseGeoInfo[ 'image_size'] otherImageSize = otherGeoInfo['image_size'] # Now estimate the bounding box of the other image in the base image topLeftCoord = projCoordToPixelCoord(otherProjBounds[0], otherProjBounds[3], baseGeoInfo) transform = MosaicUtilities.SpatialTransform() transform.setShift(topLeftCoord[0], topLeftCoord[1]) transform.write(outputPath) return topLeftCoord
def _estimateRegistration(self, baseImage, otherImage, outputPath): '''Writes an estimated registration transform to a file based on geo metadata.''' # This function assumes the images are in the same projection system! # Get the projection bounds and size in both images baseGeoInfo = IrgGeoFunctions.getImageGeoInfo(baseImage, False) otherGeoInfo = IrgGeoFunctions.getImageGeoInfo(otherImage, False) baseProjBounds = baseGeoInfo['projection_bounds'] otherProjBounds = otherGeoInfo['projection_bounds'] baseImageSize = baseGeoInfo['image_size'] otherImageSize = otherGeoInfo['image_size'] # Now estimate the bounding box of the other image in the base image topLeftCoord = projCoordToPixelCoord(otherProjBounds[0], otherProjBounds[3], baseGeoInfo) transform = MosaicUtilities.SpatialTransform() transform.setShift(topLeftCoord[0], topLeftCoord[1]) transform.write(outputPath) return topLeftCoord
def getPixelToGdcTransform(imagePath, pixelToProjectedTransform=None): '''Returns a pixel to GDC transform. The input image must either be a nicely georegistered image from Earth Engine or a pixel to projected coordinates transform must be provided.''' if pixelToProjectedTransform: # Have image to projected transform, convert it to an image to GDC transform. # Use the simple file info call (the input file may not have geo information) (width, height) = IrgGeoFunctions.getImageSize(imagePath) imagePoints = [] gdcPoints = [] # Loop through a spaced out grid of pixels in the image pointPixelSpacing = (width + height) / 20 # Results in about 100 points for r in range(0, width, pointPixelSpacing): for c in range(0, height, pointPixelSpacing): # This pixel --> projected coords --> lonlat coord thisPixel = numpy.array([float(c), float(r)]) projectedCoordinate = pixelToProjectedTransform.forward( thisPixel) gdcCoordinate = transform.metersToLatLon(projectedCoordinate) imagePoints.append(thisPixel) gdcPoints.append(gdcCoordinate) # Solve for a transform with all of these point pairs pixelToGdcTransform = transform.getTransform( numpy.asarray(gdcPoints), numpy.asarray(imagePoints)) else: # Using a reference image from EE which will have nice bounds. # Use the more thorough file info call stats = IrgGeoFunctions.getImageGeoInfo(imagePath, False) (width, height) = stats['image_size'] (minLon, maxLon, minLat, maxLat) = stats['lonlat_bounds'] # Make a transform from ref pixel to GDC using metadata on disk xScale = (maxLon - minLon) / width yScale = (maxLat - minLat) / height transformMatrix = numpy.array([[xScale, 0, minLon], [0, -yScale, maxLat], [0, 0, 1]]) pixelToGdcTransform = transform.LinearTransform(transformMatrix) return pixelToGdcTransform
def writeLabelFile(imagePath, outputPath, dataSetName, versionId, description, extraData=None): """Write out a .LBL file formatted for the PDS""" # Call functions to automatically obtain some data from the referenced image imageSize = IrgGeoFunctions.getImageSize(imagePath) boundingBox = IrgGeoFunctions.getImageBoundingBox(imagePath) # Obtain the ASP version string aspVersionString = IrgAspFunctions.getAspVersionStrings() imageGeoInfo = IrgGeoFunctions.getImageGeoInfo(imagePath) projCenterLatitude = imageGeoInfo['standard_parallel_1'] projCenterLongitude = imageGeoInfo['central_meridian'] # Currently assuming pixels are the same size metersPerPixel = abs(imageGeoInfo['pixel size'][0]) # Compute pixels per degree lonSpanDegrees = boundingBox[1] - boundingBox[0] latSpanDegrees = boundingBox[3] - boundingBox[2] pixelsPerDegreeLon = imageSize[0] / lonSpanDegrees pixelsPerDegreeLat = imageSize[1] / latSpanDegrees pixelsPerDegree = (pixelsPerDegreeLat + pixelsPerDegreeLon) / 2.0 # Computed by dividing 'Origin' by 'Pixel Size' lineProjOffset = imageGeoInfo['origin'][0] / imageGeoInfo['pixel size'][1] sampleProjOffset = imageGeoInfo['origin'][1] / imageGeoInfo['pixel size'][0] labelFile = open(outputPath, 'w') labelFile.write('PDS_VERSION_ID = PDS3\n') labelFile.write('/* The source image data definition. */\n') labelFile.write('^IMAGE = ' + os.path.basename(outputPath) +'\n') labelFile.write('/* Identification Information */\n') labelFile.write('DATA_SET_ID = ""\n') # Someone will tell us what to put here labelFile.write('DATA_SET_NAME = ""\n') # Someone will tell us what to put here labelFile.write("VOLUME_ID = ''\n") # Someone will tell us what to put here labelFile.write('PRODUCER_INSTITUTION_NAME = "NASA AMES RESEARCH CENTER"\n') labelFile.write('PRODUCER_ID = NASA IRG\n') labelFile.write('PRODUCER_FULL_NAME = "ZACHARY MORATTO"\n') labelFile.write("PRODUCT_ID = " + dataSetName + "\n") labelFile.write("PRODUCT_VERSION_ID = " + versionId + "\n") labelFile.write('PRODUCT_TYPE = "RDR"\n') labelFile.write('INSTRUMENT_HOST_NAME = "LUNAR RECONNAISSANCE ORBITER"\n') labelFile.write('INSTRUMENT_HOST_ID = "LRO"\n') labelFile.write('INSTRUMENT_NAME = "LUNAR RECONNAISSANCE ORBITER CAMERA"\n') labelFile.write('INSTRUMENT_ID = "LROC"\n') labelFile.write('TARGET_NAME = MOON\n') labelFile.write('MISSION_PHASE_NAME = "NOMINAL MISSION"\n') labelFile.write("""RATIONALE_DESC = "Created at the request of NASA's Exploration\n""") labelFile.write(' Systems Mission Directorate to support future\n') labelFile.write(' human exploration"\n') labelFile.write('SOFTWARE_NAME = "'+ aspVersionString[0] +' | '+ aspVersionString[2] +'"\n') labelFile.write('DESCRIPTION = "' + description + '"\n') labelFile.write('\n') labelFile.write('/* Time Parameters */\n') labelFile.write('PRODUCT_CREATION_TIME = ' + time.strftime("%Y-%m-%dT%H:%M:%S") + '\n') labelFile.write('\n') labelFile.write('/* NOTE: */\n') labelFile.write('/* This raster image is composed of a set of pixels that represent finite */\n') labelFile.write('/* areas, and not discrete points. The center of the upper left pixel is */\n') labelFile.write('/* defined as line and sample (1.0,1.0). The */\n') labelFile.write('/* [LINE,SAMPLE]_PROJECTION_OFFSET elements are the pixel offset from line */\n') labelFile.write('/* and sample (1.0,1.0) to the map projection origin (defined by the */\n') labelFile.write('/* CENTER_LATITUDE and CENTER_LONGITUDE elements). These offset values */\n') labelFile.write('/* are positive when the map projection origin is to the right or below */\n') labelFile.write('/* the center of the upper left pixel. */\n') if extraData: # Location for additional notes labelFile.write(extraData) labelFile.write('\n') labelFile.write('OBJECT = IMAGE_MAP_PROJECTION\n') labelFile.write(' MAP_PROJECTION_TYPE = EQUIRECTANGULAR\n') # Specified by +proj=eqc labelFile.write(' PROJECTION_LATITUDE_TYPE = PLANETOCENTRIC\n') #From gdalinfo? labelFile.write(' A_AXIS_RADIUS = 1737.4 <KM>\n') # Fixed lunar radius labelFile.write(' B_AXIS_RADIUS = 1737.4 <KM>\n') labelFile.write(' C_AXIS_RADIUS = 1737.4 <KM>\n') labelFile.write(' COORDINATE_SYSTEM_NAME = PLANETOCENTRIC\n') #From gdalinfo? labelFile.write(' POSITIVE_LONGITUDE_DIRECTION = EAST\n') #From gdalinfo? labelFile.write(' KEYWORD_LATITUDE_TYPE = PLANETOCENTRIC\n') #From gdalinfo? labelFile.write(' /* NOTE: CENTER_LATITUDE and CENTER_LONGITUDE describe the location */\n') labelFile.write(' /* of the center of projection, which is not necessarily equal to the */\n') labelFile.write(' /* location of the center point of the image. */\n') labelFile.write(' CENTER_LATITUDE = ' + str(projCenterLatitude) + ' <DEG>\n') labelFile.write(' CENTER_LONGITUDE = ' + str(projCenterLongitude) + ' <DEG>\n') labelFile.write(' LINE_FIRST_PIXEL = 1\n') labelFile.write(' LINE_LAST_PIXEL = ' + str(imageSize[1] + 1) + '\n') labelFile.write(' SAMPLE_FIRST_PIXEL = 1\n') labelFile.write(' SAMPLE_LAST_PIXEL = ' + str(imageSize[0] + 1) + '\n') labelFile.write(' MAP_PROJECTION_ROTATION = 0.0 <DEG>\n') #From gdalinfo (probably always zero) labelFile.write(' MAP_RESOLUTION = ' + str(round(pixelsPerDegree,2)) +' <PIX/DEG>\n') labelFile.write(' MAP_SCALE = ' + str(round(metersPerPixel,4)) + ' <METERS/PIXEL>\n') labelFile.write(' MAXIMUM_LATITUDE = ' + str(boundingBox[3]) + ' <DEG>\n') labelFile.write(' MINIMUM_LATITUDE = ' + str(boundingBox[2]) + ' <DEG>\n') labelFile.write(' EASTERNMOST_LONGITUDE = ' + str(boundingBox[0]) + ' <DEG>\n') labelFile.write(' WESTERNMOST_LONGITUDE = ' + str(boundingBox[1]) + ' <DEG>\n') labelFile.write(' LINE_PROJECTION_OFFSET = ' + str(round(lineProjOffset,2)) +' <PIXEL>\n') labelFile.write(' SAMPLE_PROJECTION_OFFSET = ' + str(round(sampleProjOffset,2)) +' <PIXEL>\n') labelFile.write('END_OBJECT = IMAGE_MAP_PROJECTION\n') labelFile.write('\n') labelFile.write('END\n') labelFile.close() return True
def __init__(self, sourceFileInfoDict, outputFolder, basemapInstance, basemapInstance180, force=False, threadPool=None): '''Set up all the low resolution HRSC products.''' setName = sourceFileInfoDict['setName'] self._logger = logging.getLogger('hrscImageManager') # Echo logging to stdout echo = logging.StreamHandler(sys.stdout) echo.setLevel(logging.DEBUG) echo.setFormatter(logging.Formatter(MosaicUtilities.LOG_FORMAT_STR)) self._logger.addHandler(echo) self._logger.info('Initializing hrscImageManager for set ' + setName) # Initialize some values to empty in case they are accessed prematurely self._tileDict = None # # Set up some paths self._setName = setName self._threadPool = threadPool self._outputFolder = outputFolder self._hrscBasePathOut = os.path.join(outputFolder, setName) self._tileFolder = self._hrscBasePathOut + '_tiles' self._lowResMaskPath = self._hrscBasePathOut + '_low_res_mask.tif' self._highResBinaryMaskPath = self._hrscBasePathOut + '_high_res_binary_mask.tif' self._highResMaskPath = self._hrscBasePathOut + '_high_res_mask.tif' self._brightnessGainsPath = self._hrscBasePathOut + '_brightness_gains.csv' self._basemapCropPath = self._hrscBasePathOut + '_local_cropped_basemap.tif' # A crop of the basemap used in several places self._basemapGrayCropPath = self._hrscBasePathOut + '_local_gray_cropped_basemap.tif' #self._colorPairPath = self._hrscBasePathOut + '_low_res_color_pairs.csv' self._basemapSpatialRegistrationPath = self._hrscBasePathOut + '_low_res_spatial_transform_basemap.csv' # Transform to the low res basemap self._croppedRegionSpatialRegistrationPath = self._hrscBasePathOut + '_cropped_region_spatial_transform.csv' # Transform to cropped region of low res basemap self._highResSpatialRegistrationPath = self._hrscBasePathOut + '_high_res_spatial_transform_basemap.csv' self._lowResSpatialCroppedRegistrationPath = self._hrscBasePathOut + '_low_res_cropped_spatial_transform.csv' # Get full list of input paths from the input dictionary # - Sort them into a fixed order defined at the top of the file self._inputHrscPaths = [] rawList = sourceFileInfoDict['allChannelPaths'] self._inputHrscPaths.append( [s for s in rawList if 're3' in s][0] ) self._inputHrscPaths.append( [s for s in rawList if 'gr3' in s][0] ) self._inputHrscPaths.append( [s for s in rawList if 'bl3' in s][0] ) self._inputHrscPaths.append( [s for s in rawList if 'ir3' in s][0] ) self._inputHrscPaths.append( [s for s in rawList if 'nd3' in s][0] ) # TODO: Always store path to regular basemap? # Determine if a 180-centered basemap should be used for image preprocessing. self._isCentered180 = (self.chooseLonCenter() == 180) if self._isCentered180: self._logger.info('HRSC image is centered around 180') self._basemapInstance = basemapInstance180 else: # Normal case, use the 0 centered basemap self._basemapInstance = basemapInstance # Record input parameters self._basemapColorPath = self._basemapInstance.getColorBasemapPath() # Path to the color low res entire base map print 'Generating low res image copies...' # TODO: Warp to the correct basemap! # Generate a copy of each input HRSC channel at the low basemap resolution self._lowResWarpedPaths = [self._warpToProjection(path, outputFolder, '_basemap_res', self._basemapInstance.getLowResMpp(), force) for path in self._inputHrscPaths] # Build up a string containing all the low res paths for convenience self._lowResPathString = '' for path in self._lowResWarpedPaths: self._lowResPathString += path + ' ' print 'Generating low resolution mask...' # Make a mask at the low resolution cmd = './makeSimpleImageMask ' + self._lowResMaskPath +' '+ self._lowResPathString MosaicUtilities.cmdRunner(cmd, self._lowResMaskPath, force) self._lowResPathStringAndMask = self._lowResPathString +' '+ self._lowResMaskPath self._lowResMaskImageSize = IrgGeoFunctions.getImageSize(self._lowResMaskPath) # Compute the HRSC bounding box # - This is a pretty good estimate based on the metadata lowResNadirPath = self._lowResWarpedPaths[HRSC_NADIR] geoInfo = IrgGeoFunctions.getImageGeoInfo(lowResNadirPath) #print geoInfo['projection_bounds'] # print geoInfo['lonlat_bounds'] if 'lonlat_bounds' in geoInfo: (minLon, maxLon, minLat, maxLat) = geoInfo['lonlat_bounds'] else: # This function is not as reliable! (minLon, maxLon, minLat, maxLat) = IrgGeoFunctions.getImageBoundingBox(lowResNadirPath) hrscBoundingBoxDegrees = MosaicUtilities.Rectangle(minLon, maxLon, minLat, maxLat) if hrscBoundingBoxDegrees.maxX < hrscBoundingBoxDegrees.minX: hrscBoundingBoxDegrees.maxX += 360 # If needed, get both lon values into 0-360 degree range if (hrscBoundingBoxDegrees.minX < 0) and self._isCentered180: # If working in the 0-360 degree space, make sure the longitude values are positive hrscBoundingBoxDegrees.minX += 360 hrscBoundingBoxDegrees.maxX += 360 print 'Estimated HRSC bounds: ' + str(hrscBoundingBoxDegrees) # Cut out a region from the basemap around the location of the HRSC image # - We record the ROI in degrees and low res pixels print 'Generating low res base basemap region around HRSC data' CROP_BUFFER_LAT = 1.0 CROP_BUFFER_LON = 1.0 self._croppedRegionBoundingBoxDegrees = copy.copy(hrscBoundingBoxDegrees) self._croppedRegionBoundingBoxDegrees.expand(CROP_BUFFER_LON, CROP_BUFFER_LAT) self._croppedRegionBoundingBoxPixels = self._basemapInstance.degreeRoiToPixelRoi( self._croppedRegionBoundingBoxDegrees, False) self._basemapInstance.makeCroppedRegionDegrees(self._croppedRegionBoundingBoxDegrees, self._basemapCropPath, force) self._makeGrayscaleImage(self._basemapCropPath, self._basemapGrayCropPath) # Compute the spatial registration from the HRSC image to the base map self._computeBaseSpatialRegistration(self._basemapInstance, lowResNadirPath, force) # Compute the brightness scaling gains relative to the cropped base map # - This is done at low resolution # - The low resolution output is smoothed out later to avoid jagged edges. cmd = ('./computeBrightnessCorrection ' + self._basemapCropPath +' '+ self._lowResPathStringAndMask +' ' + self._lowResSpatialCroppedRegistrationPath +' '+ self._brightnessGainsPath) MosaicUtilities.cmdRunner(cmd, self._brightnessGainsPath, force) print 'Finished with low resolution processing for HRSC set ' + setName
def main(argsIn): print '#################################################################################' print "Running makeDemAndCompare.py" try: try: usage = "usage: makeDemAndCompare.py [--output <path>][--manual]\n " parser = optparse.OptionParser(usage=usage) inputGroup = optparse.OptionGroup(parser, 'Input Paths') inputGroup.add_option("--left", dest="leftPath", help="Path to left cube file") inputGroup.add_option("--right", dest="rightPath", help="Path to right cube file") inputGroup.add_option("--lola", dest="lolaPath", help="Path to LOLA DEM") inputGroup.add_option("--asu", dest="asuPath", help="Path to ASU DEM") inputGroup.add_option("--node-file", dest="nodeFilePath", help="Path to file containing list of available nodes") parser.add_option_group(inputGroup) # The default working directory path is kind of ugly... parser.add_option("--workDir", dest="workDir", help="Folder to store temporary files in") parser.add_option("--prefix", dest="prefix", help="Output prefix.") parser.add_option("--log-path", dest="logPath", help="Where to write the output log file.") parser.add_option("--crop", dest="cropAmount", help="Crops the output image to reduce processing time.") parser.add_option("--manual", action="callback", callback=man, help="Read the manual.") parser.add_option("--keep", action="store_true", dest="keep", help="Do not delete the temporary files.") (options, args) = parser.parse_args(argsIn) if not options.leftPath: parser.error("Need left input path") if not options.rightPath: parser.error("Need right input path") if not options.prefix: parser.error("Need output prefix") except optparse.OptionError, msg: raise Usage(msg) print "Beginning processing....." startTime = time.time() # Make sure we have all the functions we need functionStartupCheck() # Set this to true to force steps after it to activate carry = False # Set up the output folders outputFolder = os.path.dirname(options.prefix) inputBaseName = os.path.basename(options.leftPath) tempFolder = outputFolder + '/' + inputBaseName + '_stereoCalibrationTemp/' if (options.workDir): tempFolder = options.workDir if not os.path.exists(outputFolder): os.mkdir(outputFolder) hadToCreateTempFolder = not os.path.exists(tempFolder) if not os.path.exists(tempFolder): os.mkdir(tempFolder) # Set up logging if not options.logPath: options.logPath = options.prefix + '-Log.txt' logging.basicConfig(filename=options.logPath,level=logging.INFO) # Go ahead and set up all the output paths # -- Deliverables demPath = options.prefix + '-DEM.tif' intersectionErrorPath = options.prefix + '-IntersectionErr.tif' hillshadePath = options.prefix + '-Hillshade.tif' colormapPath = options.prefix + '-Colormap.tif' colormapLegendPath = options.prefix + '-ColormapLegend.csv' mapProjectLeftPath = options.prefix + '-MapProjLeft.tif' mapProjectRightPath = options.prefix + '-MapProjRight.tif' confidenceLevelPath = options.prefix + '-Confidence.tif' confidenceLegendPath = options.prefix + '-ConfidenceLegend.csv' # -- Diagnostic intersectionViewPathX = options.prefix + '-IntersectionErrorX.tif' intersectionViewPathY = options.prefix + '-IntersectionErrorY.tif' intersectionViewPathZ = options.prefix + '-IntersectionErrorZ.tif' lolaDiffStatsPath = options.prefix + '-LOLA_diff_stats.txt' lolaDiffPointsPath = options.prefix + '-LOLA_diff_points.csv' lolaAsuDiffStatsPath = options.prefix + '-ASU_LOLA_diff_stats.txt' lolaAsuDiffPointsPath = options.prefix + '-ASU_LOLA_diff_points.csv' mapProjectLeftUint8Path = options.prefix + '-MapProjLeftUint8.tif' mapProjectRightUint8Path = options.prefix + '-MapProjRightUint8.tif' # If specified, crop the inputs that will be passed into the stereo function to reduce processing time mainMosaicCroppedPath = os.path.join(tempFolder, 'mainMosaicCropped.cub') stereoMosaicCroppedPath = os.path.join(tempFolder, 'stereoMosaicCropped.cub') if options.cropAmount and (options.cropAmount > 0): # Verify input files are present if not os.path.exists(options.leftPath): raise Exception('Input file ' + options.leftPath + ' not found!') if not os.path.exists(options.rightPath): raise Exception('Input file ' + options.rightPath + ' not found!') if (not os.path.exists(mainMosaicCroppedPath)) or carry: cmd = ('crop from= ' + options.leftPath + ' to= ' + mainMosaicCroppedPath + ' nlines= ' + str(options.cropAmount))# + ' line=24200') print cmd os.system(cmd) if (not os.path.exists(stereoMosaicCroppedPath) or carry): cmd = ('crop from= ' + options.rightPath + ' to= ' + stereoMosaicCroppedPath + ' nlines= ' + str(options.cropAmount))# + ' line=24200') print cmd os.system(cmd) options.leftPath = mainMosaicCroppedPath options.rightPath = stereoMosaicCroppedPath print '\n-------------------------------------------------------------------------\n' # Call stereo to generate a point cloud from the two images # - This step takes a really long time. stereoOutputPrefix = os.path.join(tempFolder, 'stereoWorkDir/stereo') stereoOutputFolder = os.path.join(tempFolder, 'stereoWorkDir') pointCloudPath = stereoOutputPrefix + '-PC.tif' stereoOptionString = ('--corr-timeout 400 --alignment-method AffineEpipolar --subpixel-mode ' + str(SUBPIXEL_MODE) + ' ' + options.leftPath + ' ' + options.rightPath + ' --job-size-w 4096 --job-size-h 4096 ' + # Reduce number of tile files created ' ' + stereoOutputPrefix + ' --processes 10 --threads-multiprocess 4' + ' --threads-singleprocess 32 --compute-error-vector' + ' --filter-mode 1' + ' --erode-max-size 5000 --subpixel-kernel 35 35 --subpixel-max-levels 0') if (not os.path.exists(pointCloudPath) and not os.path.exists(demPath)) or carry: # Verify input files are present if not os.path.exists(options.leftPath): raise Exception('Input file ' + options.leftPath + ' not found!') if not os.path.exists(options.rightPath): raise Exception('Input file ' + options.rightPath + ' not found!') cmd = ('parallel_stereo ' + stereoOptionString) print cmd os.system(cmd) # Compute percentage of good pixels percentGood = IrgAspFunctions.getStereoGoodPixelPercentage(stereoOutputPrefix) print 'Stereo completed with good pixel percentage: ' + str(percentGood) logging.info('Final stereo completed with good pixel percentage: %s', str(percentGood)) else: print 'Stereo file ' + pointCloudPath + ' already exists, skipping stereo step.' stereoTime = time.time() logging.info('Stereo finished in %f seconds', stereoTime - startTime) # Find out the center latitude of the mosaic if os.path.exists(options.leftPath): centerLat = IrgIsisFunctions.getCubeCenterLatitude(options.leftPath, tempFolder) elif os.path.exists(demPath): # Input file has been deleted but we still have the info demInfo = IrgGeoFunctions.getImageGeoInfo(demPath, False) centerLat = demInfo['standard_parallel_1'] else: raise Exception("Can't delete the input files before creating the DEM!") # Generate a DEM if (not os.path.exists(demPath)) or carry: # Equirectangular style projection # - Latitude of true scale = center latitude = lat_ts # - Latitude of origin = 0 = lat+0 # - Longitude of projection center = Central meridian = lon+0 cmd = ('point2dem --dem-hole-fill-len 15 --remove-outliers --errorimage -o ' + options.prefix + ' ' + pointCloudPath + ' -r moon --tr ' + str(DEM_METERS_PER_PIXEL) + ' --t_srs "+proj=eqc +lat_ts=' + str(centerLat) + ' +lat_0=0 +a='+str(MOON_RADIUS)+' +b='+str(MOON_RADIUS)+' +units=m" --nodata ' + str(DEM_NODATA)) os.system(cmd) else: print 'DEM file ' + demPath + ' already exists, skipping point2dem step.' # Create a hillshade image to visualize the output if (not os.path.exists(hillshadePath)) or carry: cmd = 'hillshade ' + demPath + ' -o ' + hillshadePath print cmd os.system(cmd) else: print 'Output file ' + hillshadePath + ' already exists, skipping hillshade step.' # Create a colorized version of the hillshade # - Uses a blue-red color map from here: http://www.sandia.gov/~kmorel/documents/ColorMaps/ if (not os.path.exists(colormapPath)) or (not os.path.exists(colormapLegendPath)) or carry: # The color LUT is kept with the source code lutFilePath = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'colorProfileBlueRed.csv') # Generate the initial version of colormap colormapTempPath = options.prefix + '-ColormapTemp.tif' cmd = 'colormap ' + demPath + ' -o ' + colormapTempPath + ' -s ' + hillshadePath + ' --lut-file ' + lutFilePath print cmd os.system(cmd) # Now convert to the final output version (remove transparency layer) and remove the temp file IrgFileFunctions.stripRgbImageAlphaChannel(colormapTempPath, colormapPath) os.remove(colormapTempPath) # Generate another file storing the colormap info writeColorMapInfo(colormapPath, lutFilePath, demPath, colormapLegendPath) else: print 'Output file ' + colormapPath + ' already exists, skipping colormap step.' ## Create a 3d mesh of the point cloud #meshPath = os.path.join(outputFolder, 'mesh.ive') #meshPrefix = os.path.join(outputFolder, 'mesh') #cmd = 'point2mesh ' + pointCloudPath + ' ' + options.leftPath + ' -o ' + meshPrefix #if not os.path.exists(meshPath): # print cmd # os.system(cmd) if not options.keep: # Remove stereo folder here to cut down on file count before mapproject calls IrgFileFunctions.removeFolderIfExists(stereoOutputFolder) # Convert the intersection error to a viewable format cmdX = 'gdal_translate -ot byte -scale 0 10 0 255 -outsize 50% 50% -b 1 ' + intersectionErrorPath + ' ' + intersectionViewPathX cmdY = 'gdal_translate -ot byte -scale 0 10 0 255 -outsize 50% 50% -b 2 ' + intersectionErrorPath + ' ' + intersectionViewPathY cmdZ = 'gdal_translate -ot byte -scale 0 10 0 255 -outsize 50% 50% -b 3 ' + intersectionErrorPath + ' ' + intersectionViewPathZ if not os.path.exists(intersectionViewPathX) or carry: print cmdX os.system(cmdX) if not os.path.exists(intersectionViewPathY) or carry: print cmdY os.system(cmdY) if not os.path.exists(intersectionViewPathZ) or carry: print cmdZ os.system(cmdZ) # Generate a confidence plot from the intersection error if not os.path.exists(confidenceLevelPath): thresholdString = str(PIXEL_ACCURACY_THRESHOLDS)[1:-1].replace(',', '') cmd = ('maskFromIntersectError ' + intersectionErrorPath + ' ' + confidenceLevelPath + ' --legend ' + confidenceLegendPath + ' --scaleOutput --thresholds ' + thresholdString) print cmd os.system(cmd) hillshadeTime = time.time() logging.info('DEM and hillshade finished in %f seconds', hillshadeTime - stereoTime) # Call script to compare LOLA data with the DEM if options.lolaPath: compareDemToLola(options.lolaPath, demPath, lolaDiffStatsPath, lolaDiffPointsPath, carry) # Call script to compare LOLA data with the ASU DEM if options.asuPath: compareDemToLola(options.lolaPath, options.asuPath, lolaAsuDiffStatsPath, lolaAsuDiffPointsPath, carry) # Generate a map projected version of the left and right images # - This step is done last since it is so slow! mapProjectImage(options.leftPath, demPath, mapProjectLeftPath, MAP_PROJECT_METERS_PER_PIXEL, centerLat, options.nodeFilePath, carry) mapProjectImage(options.rightPath, demPath, mapProjectRightPath, MAP_PROJECT_METERS_PER_PIXEL, centerLat, options.nodeFilePath, carry) # Generate 8 bit versions of the mapproject files for debugging cmdLeft = 'gdal_translate -scale -ot byte ' + mapProjectLeftPath + ' ' + mapProjectLeftUint8Path cmdRight = 'gdal_translate -scale -ot byte ' + mapProjectRightPath + ' ' + mapProjectRightUint8Path if not os.path.exists(mapProjectLeftUint8Path) or carry: print cmdLeft os.system(cmdLeft) if not os.path.exists(mapProjectRightUint8Path) or carry: print cmdRight os.system(cmdRight) mapProjectTime = time.time() logging.info('Map project finished in %f seconds', mapProjectTime - hillshadeTime) # Clean up temporary files if not options.keep: print 'Removing temporary files' IrgFileFunctions.removeIfExists(mainMosaicCroppedPath) IrgFileFunctions.removeIfExists(stereoMosaicCroppedPath) #IrgFileFunctions.removeIntermediateStereoFiles(stereoOutputPrefix) # Limited clear IrgFileFunctions.removeFolderIfExists(stereoOutputFolder) # Larger clear #if (hadToCreateTempFolder): Not done since stereo output needs to be retained # IrgFileFunctions.removeFolderIfExists(tempFolder) endTime = time.time() logging.info('makeDemAndCompare.py finished in %f seconds', endTime - startTime) print "Finished in " + str(endTime - startTime) + " seconds." print '#################################################################################' return 0
def __init__(self, sourceFileInfoDict, outputFolder, basemapInstance, basemapInstance180, force=False, threadPool=None): '''Set up all the low resolution HRSC products.''' setName = sourceFileInfoDict['setName'] self._logger = logging.getLogger('hrscImageManager') # Echo logging to stdout echo = logging.StreamHandler(sys.stdout) echo.setLevel(logging.DEBUG) echo.setFormatter(logging.Formatter(MosaicUtilities.LOG_FORMAT_STR)) self._logger.addHandler(echo) self._logger.info('Initializing hrscImageManager for set ' + setName) # Initialize some values to empty in case they are accessed prematurely self._tileDict = None # # Set up some paths self._setName = setName self._threadPool = threadPool self._outputFolder = outputFolder self._hrscBasePathOut = os.path.join(outputFolder, setName) self._tileFolder = self._hrscBasePathOut + '_tiles' self._lowResMaskPath = self._hrscBasePathOut + '_low_res_mask.tif' self._highResBinaryMaskPath = self._hrscBasePathOut + '_high_res_binary_mask.tif' self._highResMaskPath = self._hrscBasePathOut + '_high_res_mask.tif' self._brightnessGainsPath = self._hrscBasePathOut + '_brightness_gains.csv' self._basemapCropPath = self._hrscBasePathOut + '_local_cropped_basemap.tif' # A crop of the basemap used in several places self._basemapGrayCropPath = self._hrscBasePathOut + '_local_gray_cropped_basemap.tif' #self._colorPairPath = self._hrscBasePathOut + '_low_res_color_pairs.csv' self._basemapSpatialRegistrationPath = self._hrscBasePathOut + '_low_res_spatial_transform_basemap.csv' # Transform to the low res basemap self._croppedRegionSpatialRegistrationPath = self._hrscBasePathOut + '_cropped_region_spatial_transform.csv' # Transform to cropped region of low res basemap self._highResSpatialRegistrationPath = self._hrscBasePathOut + '_high_res_spatial_transform_basemap.csv' self._lowResSpatialCroppedRegistrationPath = self._hrscBasePathOut + '_low_res_cropped_spatial_transform.csv' # Get full list of input paths from the input dictionary # - Sort them into a fixed order defined at the top of the file self._inputHrscPaths = [] rawList = sourceFileInfoDict['allChannelPaths'] self._inputHrscPaths.append([s for s in rawList if 're3' in s][0]) self._inputHrscPaths.append([s for s in rawList if 'gr3' in s][0]) self._inputHrscPaths.append([s for s in rawList if 'bl3' in s][0]) self._inputHrscPaths.append([s for s in rawList if 'ir3' in s][0]) self._inputHrscPaths.append([s for s in rawList if 'nd3' in s][0]) # TODO: Always store path to regular basemap? # Determine if a 180-centered basemap should be used for image preprocessing. self._isCentered180 = (self.chooseLonCenter() == 180) if self._isCentered180: self._logger.info('HRSC image is centered around 180') self._basemapInstance = basemapInstance180 else: # Normal case, use the 0 centered basemap self._basemapInstance = basemapInstance # Record input parameters self._basemapColorPath = self._basemapInstance.getColorBasemapPath( ) # Path to the color low res entire base map print 'Generating low res image copies...' # TODO: Warp to the correct basemap! # Generate a copy of each input HRSC channel at the low basemap resolution self._lowResWarpedPaths = [ self._warpToProjection(path, outputFolder, '_basemap_res', self._basemapInstance.getLowResMpp(), force) for path in self._inputHrscPaths ] # Build up a string containing all the low res paths for convenience self._lowResPathString = '' for path in self._lowResWarpedPaths: self._lowResPathString += path + ' ' print 'Generating low resolution mask...' # Make a mask at the low resolution cmd = './makeSimpleImageMask ' + self._lowResMaskPath + ' ' + self._lowResPathString MosaicUtilities.cmdRunner(cmd, self._lowResMaskPath, force) self._lowResPathStringAndMask = self._lowResPathString + ' ' + self._lowResMaskPath self._lowResMaskImageSize = IrgGeoFunctions.getImageSize( self._lowResMaskPath) # Compute the HRSC bounding box # - This is a pretty good estimate based on the metadata lowResNadirPath = self._lowResWarpedPaths[HRSC_NADIR] geoInfo = IrgGeoFunctions.getImageGeoInfo(lowResNadirPath) #print geoInfo['projection_bounds'] # print geoInfo['lonlat_bounds'] if 'lonlat_bounds' in geoInfo: (minLon, maxLon, minLat, maxLat) = geoInfo['lonlat_bounds'] else: # This function is not as reliable! (minLon, maxLon, minLat, maxLat) = IrgGeoFunctions.getImageBoundingBox(lowResNadirPath) hrscBoundingBoxDegrees = MosaicUtilities.Rectangle( minLon, maxLon, minLat, maxLat) if hrscBoundingBoxDegrees.maxX < hrscBoundingBoxDegrees.minX: hrscBoundingBoxDegrees.maxX += 360 # If needed, get both lon values into 0-360 degree range if (hrscBoundingBoxDegrees.minX < 0) and self._isCentered180: # If working in the 0-360 degree space, make sure the longitude values are positive hrscBoundingBoxDegrees.minX += 360 hrscBoundingBoxDegrees.maxX += 360 print 'Estimated HRSC bounds: ' + str(hrscBoundingBoxDegrees) # Cut out a region from the basemap around the location of the HRSC image # - We record the ROI in degrees and low res pixels print 'Generating low res base basemap region around HRSC data' CROP_BUFFER_LAT = 1.0 CROP_BUFFER_LON = 1.0 self._croppedRegionBoundingBoxDegrees = copy.copy( hrscBoundingBoxDegrees) self._croppedRegionBoundingBoxDegrees.expand(CROP_BUFFER_LON, CROP_BUFFER_LAT) self._croppedRegionBoundingBoxPixels = self._basemapInstance.degreeRoiToPixelRoi( self._croppedRegionBoundingBoxDegrees, False) self._basemapInstance.makeCroppedRegionDegrees( self._croppedRegionBoundingBoxDegrees, self._basemapCropPath, force) self._makeGrayscaleImage(self._basemapCropPath, self._basemapGrayCropPath) # Compute the spatial registration from the HRSC image to the base map self._computeBaseSpatialRegistration(self._basemapInstance, lowResNadirPath, force) # Compute the brightness scaling gains relative to the cropped base map # - This is done at low resolution # - The low resolution output is smoothed out later to avoid jagged edges. cmd = ('./computeBrightnessCorrection ' + self._basemapCropPath + ' ' + self._lowResPathStringAndMask + ' ' + self._lowResSpatialCroppedRegistrationPath + ' ' + self._brightnessGainsPath) MosaicUtilities.cmdRunner(cmd, self._brightnessGainsPath, force) print 'Finished with low resolution processing for HRSC set ' + setName