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    
Exemple #2
0
    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
Exemple #3
0
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
Exemple #7
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