def getBoundingBox(fileList):
    """Return the bounding box for this data set in the format (minLon, maxLon, minLat, maxLat)"""
    if len(fileList) == 3: # ASU method
        # Read plain text from the label file
        return IrgGeoFunctions.getBoundingBoxFromIsisLabel(fileList[-1])
    else: # Compute the corners from a geotiff
        return IrgGeoFunctions.getGeoTiffBoundingBox(fileList[0])
def getBoundingBox(fileList):
    """Return the bounding box for this data set in the format (minLon, maxLon, minLat, maxLat)"""
    if len(fileList) == 3:  # ASU method
        # Read plain text from the label file
        return IrgGeoFunctions.getBoundingBoxFromIsisLabel(fileList[-1])
    else:  # Compute the corners from a geotiff
        return IrgGeoFunctions.getGeoTiffBoundingBox(fileList[0])
def getBoundingBox(fileList):
    """Return the bounding box for this data set in the format (minLon, maxLon, minLat, maxLat)"""
    if len(fileList) == 2:  # Read BB from the label file
        return IrgGeoFunctions.getBoundingBoxFromIsisLabel(fileList[1])
    else:  # No label file, read it from the the main file
        return IrgGeoFunctions.getImageBoundingBox(
            fileList[0]
        )  # This information is also available in the IMG file header
def writeColorMapInfo(colormapPath, lutFilePath, demPath, outputPath):
    """Writes a file containing the color map information"""
    
    colormapPercentiles = []
    numColorSteps = IrgFileFunctions.getFileLineCount(lutFilePath)
    for i in range(0,numColorSteps): # This loop generates the percentage values from the color profile we are using
        colormapPercentiles.append(i / float(numColorSteps-1))
    # Get the min and max elevation of the DEM
    elevationBounds = IrgGeoFunctions.getImageStats(demPath) 
    # Get elevation values for each percentile
    colormapPercentileValues = IrgMathFunctions.getPercentileValues(elevationBounds[0][0], elevationBounds[0][1], colormapPercentiles)
    
    # Now write out a version of the LUT file with values added
    inputFile  = open(lutFilePath, 'r')
    outputFile = open(outputPath,  'w')

    # Add a header line to the output file
    outputFile.write('Percent of range, R, G, B, Elevation (meters above datum)\n')

    # Write a copy of the input file with the elevation values appended to each line    
    for (line, value) in zip(inputFile, colormapPercentileValues):
        newLine = line[:-1] + ', ' + str(round(value,2)) + '\n'
        outputFile.write(newLine) 
        
    inputFile.close()
    outputFile.close()
def correctPixelCoordinates(registrationResult):
    '''Rescales the pixel coordinates based on the resolution they were collected at
       compared to the full image resolution.'''

    # TODO: Account for the image labels adjusting the image size!

    sourceHeight = registrationResult['manualImageHeight']
    sourceWidth = registrationResult['manualImageWidth']

    (outputWidth, outputHeight) = IrgGeoFunctions.getImageSize(
        registrationResult['sourceImagePath'])

    if (sourceHeight != outputHeight) or (sourceWidth != outputWidth):

        # Compute rescale
        heightScale = float(outputHeight) / float(sourceHeight)
        widthScale = float(outputWidth) / float(sourceWidth)

        # Apply to each of the pixel coordinates
        out = []
        for pixel in registrationResult['imageInliers']:
            newPixel = (pixel[0] * widthScale, pixel[1] * heightScale)
            out.append(newPixel)
        registrationResult['imageInliers'] = out

    return registrationResult
Exemple #6
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 chooseLonCenter(self):
     '''Choose whether to align to the 0 centered or 180 centered basemap'''
     (minLon, maxLon, minLat, maxLat) = IrgGeoFunctions.getImageBoundingBox(self._inputHrscPaths[0])
     meanLon = (minLon + maxLon) / 2 # TODO: Verify how these bounds work!
     # Detect if the image is located nearby the 180 degree line
     if (abs(abs(meanLon)-180) < 10) or (abs(maxLon - minLon) > 200):
         return 180
     else: # Use the normal 0 centered image
         return 0
Exemple #8
0
 def chooseLonCenter(self):
     '''Choose whether to align to the 0 centered or 180 centered basemap'''
     (minLon, maxLon, minLat,
      maxLat) = IrgGeoFunctions.getImageBoundingBox(self._inputHrscPaths[0])
     meanLon = (minLon + maxLon) / 2  # TODO: Verify how these bounds work!
     # Detect if the image is located nearby the 180 degree line
     if (abs(abs(meanLon) - 180) < 10) or (abs(maxLon - minLon) > 200):
         return 180
     else:  # Use the normal 0 centered image
         return 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 #10
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 #11
0
def cropImageLabel(jpegPath, outputPath):
    '''Create a copy of a jpeg file with any label cropped off'''

    # Check if there is a label using a simple command line tool
    cmdPath = settings.PROJ_ROOT + '/apps/georef_imageregistration/build/detectImageTag'
    cmd = [cmdPath, jpegPath]
    print cmd
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    textOutput, err = p.communicate()

    if 'NO_LABEL' in textOutput:
        # The file is fine, just copy it.
        print 'Copy ' + jpegPath + ' --> ' + outputPath
        try:
            shutil.copy(jpegPath, outputPath)
        except:
            print 'Copy failed, try again!'
            #            shutil.copy(jpegPath, outputPath)
            os.system('cp ' + jpegPath + ' ' + outputPath)
            if not os.path.exists(outputPath):
                raise Exception('Still failed!')
            print 'Retry successful!'
    else:
        lines = textOutput.strip().split(
            '\n')  # Get the parts of the last line
        parts = lines[-1].split()
        if len(parts) != 3:
            raise Exception('Error running detectImageTag, got response: ' +
                            textOutput)
        side = parts[1]
        labelPos = int(parts[2])
        print 'Detected image label: ' + side + ' at index ' + str(labelPos)
        # Trim the label off of the bottom of the image
        imageSize = IrgGeoFunctions.getImageSize(jpegPath)
        x = 0
        y = 0
        width = imageSize[0]
        height = imageSize[1]
        if side == 'LEFT':
            x = labelPos
            width = width - labelPos
        if side == 'RIGHT':
            width = labelPos
        if side == 'TOP':
            y = labelPos
            height = height - labelPos
        if side == 'BOTTOM':
            height = labelPos
        cmd = ('gdal_translate -of jpeg -srcwin %d %d %d %d %s %s' %
               (x, y, width, height, jpegPath, outputPath))
        print cmd
        os.system(cmd)
Exemple #12
0
def recordOutputImages(sourceImagePath,
                       exifSourcePath,
                       outputPrefix,
                       imageInliers,
                       gdcInliers,
                       minUncertaintyMeters,
                       centerPointSource,
                       isManualRegistration=False,
                       overwrite=True):
    '''Generates all the output image files that we create for each successfully processed image.'''

    # We generate two pairs of images, one containing the image data
    #  and another with the same format but containing the uncertainty distances.
    outputPrefix = outputPrefix + '-' + centerPointSource
    uncertaintyOutputPrefix = outputPrefix + '-uncertainty'
    rawUncertaintyPath = outputPrefix + '-uncertainty_raw.tif'

    # Create the raw uncertainty image
    (width, height) = IrgGeoFunctions.getImageSize(sourceImagePath)
    posError = generateUncertaintyImage(width, height, imageInliers,
                                        minUncertaintyMeters,
                                        rawUncertaintyPath)

    # Get a measure of the fit error
    fitError = getFitError(imageInliers, gdcInliers)

    # Generate the two pairs of images in the same manner
    try:
        (noWarpOutputPath, warpOutputPath) = \
            generateGeotiff(sourceImagePath, outputPrefix, imageInliers, gdcInliers,
                                            posError, fitError, isManualRegistration,
                                            exifSourcePath,
                                            writeHeaders=True, overwrite=True)
    except Exception as e:
        print str(e)

    try:
        (noWarpOutputPath, warpOutputPath) = \
            generateGeotiff(rawUncertaintyPath, uncertaintyOutputPrefix, imageInliers, gdcInliers,
                                                posError, fitError, isManualRegistration,
                                                exifSourcePath,
                                                writeHeaders=False, overwrite=True)
    except Exception as e:
        print str(e)

    # Clean up the raw uncertainty image and any extraneous files
    rawXmlPath = rawUncertaintyPath + '.aux.xml'
    os.remove(rawUncertaintyPath)
    if os.path.exists(rawXmlPath):
        os.remove(rawXmlPath)
def splitImageGdal(imagePath, outputPrefix, tileSize, force=False, pool=None, maskList=[]):
    '''gdal_translate based replacement for the ImageMagick convert based image split.
       This function assumes that all relevant folders have been created.'''

    # Compute the bounding box for each tile
    inputImageSize    = IrgGeoFunctions.getImageSize(imagePath)
    #print imagePath + ' is size ' + str(inputImageSize)
    numTilesX = int(math.ceil(float(inputImageSize[0]) / float(tileSize)))
    numTilesY = int(math.ceil(float(inputImageSize[1]) / float(tileSize)))
    print 'Using gdal_translate to generate ' + str(int(numTilesX*numTilesY)) + ' tiles!'
    
    # Generate each of the tiles using GDAL
    cmdList = []
    for r in range(0,numTilesY):
        for c in range(0,numTilesX):
            
            if maskList: # Make sure this tile is not completely masked out
                # Find the info for the corresponding mask tile
                # If the image is larger than the mask, there is a chance there will be no
                #  mask tile for this image tile.  If so we can still skip the image tile,,
                tilePrefix   = getTilePrefix(r, c)
                maskTileInfo = [x for x in maskList if x['prefix'] == tilePrefix]
                if not maskTileInfo or (maskTileInfo[0]['percentValid'] < MIN_TILE_PERCENT_PIXELS_VALID):
                    continue
            
            # Get the pixel ROI for this tile
            # - TODO: Use the Tiling class!
            minCol = c*tileSize
            minRow = r*tileSize
            width  = tileSize
            height = tileSize
            if (minCol + width ) > inputImageSize[0]: width  = inputImageSize[0] - minCol
            if (minRow + height) > inputImageSize[1]: height = inputImageSize[1] - minRow
            totalNumPixels  = height*width
            
            # Generate the tile (console output suppressed)
            thisPixelRoi = ('%d %d %d %d' % (minCol, minRow, width, height))
            thisTilePath = outputPrefix + str(r) +'_'+ str(c) + '.tif'
            cmd = MosaicUtilities.GDAL_TRANSLATE_PATH+' -q -srcwin ' + thisPixelRoi +' '+ imagePath +' '+ thisTilePath
            if pool:
                #print cmd
                cmdList.append((cmd, thisTilePath, force))
            else:
                MosaicUtilities.cmdRunner(cmd, thisTilePath, force)

    if pool:
        # Pass all of these commands to a multiprocessing worker pool
        print 'splitImageGdal is launching '+str(len(cmdList))+' gdalwarp threads...'
        pool.map(MosaicUtilities.cmdRunnerWrapper, cmdList)
Exemple #14
0
def main():

    outputPath = ''

    try:
        try:
            usage = "usage: cube2kml.py [--help][--manual]\n  "
            parser = optparse.OptionParser(usage=usage)
            parser.add_option("--manual",
                              action="callback",
                              callback=man,
                              help="Read the manual.")
            parser.add_option(
                "-o",
                "--output-path",
                dest="outputPath",
                help="Output path (default replace extension with .kml")
            (options, args) = parser.parse_args()

            if not args: parser.error("need input cube file")

            cubePath = args[0]

        except optparse.OptionError, msg:
            raise Usage(msg)

        print "Beginning processing....."

        # Determine the output path
        if outputPath == '':
            outputPath = os.path.splitext(
                cubePath)[0] + '.kml'  # Default output path
        outputFolder = os.path.dirname(outputPath)

        # Get the four corners of the cube
        bb = IrgGeoFunctions.getImageBoundingBox(cubePath)

        # Generate a kml plot of the cube
        generateKml(bb, outputPath)

        print "Finished"
        return 0
Exemple #15
0
def generateTileInfo(fullPath, fileName, tileSize, metadataPath, force=False):
    '''Generates a metadata json file for an image tile'''

    # If the metadata is saved, just reload it.
    if (os.path.exists(metadataPath) and not force):
        with open(metadataPath, 'r') as f:
            thisTileInfo = json.load(f)
        return thisTileInfo

    # Figure out the position of the tile
    numbers = re.findall(r"[\d']+",
                         fileName)  # Extract all numbers from the file name
    tileRow = int(numbers[-2])  # In the tile grid
    tileCol = int(numbers[-1])
    pixelRow = tileRow * tileSize  # In pixel coordinates relative to the original image
    pixelCol = tileCol * tileSize

    # TODO: Counting the black pixels is a little slow, run this in parallel!
    # Get other tile information
    width, height = IrgGeoFunctions.getImageSize(fullPath)
    totalNumPixels = height * width
    blackPixelCount = MosaicUtilities.countBlackPixels(fullPath)
    validPercentage = 1.0 - (float(blackPixelCount) / float(totalNumPixels))

    thisTileInfo = {
        'path': fullPath,
        'tileRow': tileRow,
        'tileCol': tileCol,
        'pixelRow': pixelRow,
        'pixelCol': pixelCol,
        'heightPixels': height,
        'widthPixels': width,
        'percentValid': validPercentage,
        'prefix': getTilePrefix(tileRow, tileCol)
    }

    # Cache the metadata to disk so we don't have to recompute
    with open(metadataPath, 'w') as f:
        json.dump(thisTileInfo, f)

    return thisTileInfo
def generateTileInfo(fullPath, fileName, tileSize, metadataPath, force=False):
    '''Generates a metadata json file for an image tile'''
    
    # If the metadata is saved, just reload it.
    if (os.path.exists(metadataPath) and not force):
        with open(metadataPath, 'r') as f:
            thisTileInfo = json.load(f)
        return thisTileInfo
    
    # Figure out the position of the tile
    numbers  =  re.findall(r"[\d']+", fileName) # Extract all numbers from the file name
    tileRow  = int(numbers[-2]) # In the tile grid
    tileCol  = int(numbers[-1])
    pixelRow = tileRow * tileSize # In pixel coordinates relative to the original image
    pixelCol = tileCol * tileSize
    
    # TODO: Counting the black pixels is a little slow, run this in parallel!
    # Get other tile information
    width, height   = IrgGeoFunctions.getImageSize(fullPath)
    totalNumPixels  = height*width
    blackPixelCount = MosaicUtilities.countBlackPixels(fullPath)
    validPercentage = 1.0 - (float(blackPixelCount) / float(totalNumPixels))
    
    thisTileInfo = {'path'        : fullPath,
                    'tileRow'     : tileRow,
                    'tileCol'     : tileCol,
                    'pixelRow'    : pixelRow,
                    'pixelCol'    : pixelCol,
                    'heightPixels': height,
                    'widthPixels' : width,
                    'percentValid': validPercentage,
                    'prefix'      : getTilePrefix(tileRow, tileCol)
                   }

    # Cache the metadata to disk so we don't have to recompute
    with open(metadataPath, 'w') as f:
        json.dump(thisTileInfo, f)
        
    return thisTileInfo
Exemple #17
0
def main():

    outputPath = ""

    try:
        try:
            usage = "usage: cube2kml.py [--help][--manual]\n  "
            parser = optparse.OptionParser(usage=usage)
            parser.add_option("--manual", action="callback", callback=man, help="Read the manual.")
            parser.add_option(
                "-o", "--output-path", dest="outputPath", help="Output path (default replace extension with .kml"
            )
            (options, args) = parser.parse_args()

            if not args:
                parser.error("need input cube file")

            cubePath = args[0]

        except optparse.OptionError, msg:
            raise Usage(msg)

        print "Beginning processing....."

        # Determine the output path
        if outputPath == "":
            outputPath = os.path.splitext(cubePath)[0] + ".kml"  # Default output path
        outputFolder = os.path.dirname(outputPath)

        # Get the four corners of the cube
        bb = IrgGeoFunctions.getImageBoundingBox(cubePath)

        # Generate a kml plot of the cube
        generateKml(bb, outputPath)

        print "Finished"
        return 0
Exemple #18
0
def getBoundingBox(fileList):
    """Return the bounding box for this data set in the format (minLon, maxLon, minLat, maxLat)"""
    return IrgGeoFunctions.getImageBoundingBox(fileList[0])
Exemple #19
0
def splitImageGdal(imagePath,
                   outputPrefix,
                   tileSize,
                   force=False,
                   pool=None,
                   maskList=[]):
    '''gdal_translate based replacement for the ImageMagick convert based image split.
       This function assumes that all relevant folders have been created.'''

    # Compute the bounding box for each tile
    inputImageSize = IrgGeoFunctions.getImageSize(imagePath)
    #print imagePath + ' is size ' + str(inputImageSize)
    numTilesX = int(math.ceil(float(inputImageSize[0]) / float(tileSize)))
    numTilesY = int(math.ceil(float(inputImageSize[1]) / float(tileSize)))
    print 'Using gdal_translate to generate ' + str(int(
        numTilesX * numTilesY)) + ' tiles!'

    # Generate each of the tiles using GDAL
    cmdList = []
    for r in range(0, numTilesY):
        for c in range(0, numTilesX):

            if maskList:  # Make sure this tile is not completely masked out
                # Find the info for the corresponding mask tile
                # If the image is larger than the mask, there is a chance there will be no
                #  mask tile for this image tile.  If so we can still skip the image tile,,
                tilePrefix = getTilePrefix(r, c)
                maskTileInfo = [
                    x for x in maskList if x['prefix'] == tilePrefix
                ]
                if not maskTileInfo or (maskTileInfo[0]['percentValid'] <
                                        MIN_TILE_PERCENT_PIXELS_VALID):
                    continue

            # Get the pixel ROI for this tile
            # - TODO: Use the Tiling class!
            minCol = c * tileSize
            minRow = r * tileSize
            width = tileSize
            height = tileSize
            if (minCol + width) > inputImageSize[0]:
                width = inputImageSize[0] - minCol
            if (minRow + height) > inputImageSize[1]:
                height = inputImageSize[1] - minRow
            totalNumPixels = height * width

            # Generate the tile (console output suppressed)
            thisPixelRoi = ('%d %d %d %d' % (minCol, minRow, width, height))
            thisTilePath = outputPrefix + str(r) + '_' + str(c) + '.tif'
            cmd = MosaicUtilities.GDAL_TRANSLATE_PATH + ' -q -srcwin ' + thisPixelRoi + ' ' + imagePath + ' ' + thisTilePath
            if pool:
                #print cmd
                cmdList.append((cmd, thisTilePath, force))
            else:
                MosaicUtilities.cmdRunner(cmd, thisTilePath, force)

    if pool:
        # Pass all of these commands to a multiprocessing worker pool
        print 'splitImageGdal is launching ' + str(
            len(cmdList)) + ' gdalwarp threads...'
        pool.map(MosaicUtilities.cmdRunnerWrapper, cmdList)
def convertTransformToGeo(imageToRefImageTransform, newImagePath, refImagePath, refImageGeoTransform=None):
    '''Converts an image-to-image homography to the ProjectiveTransform
       class used elsewhere in geocam.
       Either the reference image must be geo-registered, or the geo transform for it
       must be provided.'''

    # Convert the image-to-image transform parameters to a class
    temp = numpy.array([imageToRefImageTransform[0:3],
                        imageToRefImageTransform[3:6],
                        imageToRefImageTransform[6:9]] )
    imageToRefTransform = transform.ProjectiveTransform(temp)

    newImageSize = IrgGeoFunctions.getImageSize(newImagePath)
    refImageSize = IrgGeoFunctions.getImageSize(refImagePath)

    # Get a pixel to GDC transform for the reference image
    refPixelToGdcTransform = registration_common.getPixelToGdcTransform(
                                                  refImagePath, refImageGeoTransform)

    # Generate a list of point pairs
    imagePoints = []
    projPoints  = []
    gdcPoints   = []
    
    
    print 'transform = \n' + str(imageToRefTransform.matrix)
    
    # Loop through an evenly spaced grid of pixels in the new image
    # - For each pixel, compute the desired output coordinate
    pointPixelSpacing = (newImageSize[0] + newImageSize[1]) / 20 # Results in about 100 points
    for r in range(0, newImageSize[0], pointPixelSpacing):
        for c in range(0, newImageSize[1], pointPixelSpacing):
            # Get pixel in new image and matching pixel in the reference image
            thisPixel       = numpy.array([float(c), float(r)])
            pixelInRefImage = imageToRefTransform.forward(thisPixel)

            # If any pixel transforms outside the reference image our transform
            # is probably invalid but continue on skipping this pixel.
            if ((not registration_common.isPixelValid(thisPixel, newImageSize)) or
                (not registration_common.isPixelValid(pixelInRefImage, refImageSize))):
                continue

            # Compute the location of this pixel in the projected coordinate system
            #  used by the transform.py file.
            if (not refImageGeoTransform):
                # Use the geo information of the reference image
                gdcCoordinate       = refPixelToGdcTransform.forward(pixelInRefImage)
                projectedCoordinate = transform.lonLatToMeters(gdcCoordinate)
            else: # Use the user-provided transform
                projectedCoordinate = refImageGeoTransform.forward(pixelInRefImage)
                gdcCoordinate       = transform.metersToLatLon(projectedCoordinate)
                
            imagePoints.append(thisPixel)
            projPoints.append(projectedCoordinate)
            gdcPoints.append(gdcCoordinate)
            #print str(thisPixel) + ' --> ' + str(gdcCoordinate) + ' <--> ' + str(projectedCoordinate)

    # Compute a transform object that converts from the new image to projected coordinates
    #print 'Converting transform to world coordinates...'
    #testImageToProjectedTransform = transform.getTransform(numpy.asarray(worldPoints),
    #                                                       numpy.asarray(imagePoints))
    testImageToProjectedTransform = transform.ProjectiveTransform.fit(numpy.asarray(projPoints),
                                                                      numpy.asarray(imagePoints))
    
    testImageToGdcTransform = transform.ProjectiveTransform.fit(numpy.asarray(gdcPoints),
                                                                numpy.asarray(imagePoints))
    
    #print refPixelToGdcTransform
    #print testImageToProjectedTransform
    #for i, w in zip(imagePoints, worldPoints):
    #    print str(i) + ' --> ' + str(w) + ' <--> ' + str(testImageToProjectedTransform.forward(i))
    
    return (testImageToProjectedTransform, testImageToGdcTransform, refPixelToGdcTransform)
def addGeoDataToAsuJp2File(inputJp2Path, inputHeaderPath, outputPath, keep=False):
    """Does the actual work of adding the geo data"""

    if not os.path.exists(inputJp2Path):
        raise Exception('Input file ' + inputJp2Path + ' does not exist!')
    if not os.path.exists(inputHeaderPath):
        raise Exception('Input file ' + inputHeaderPath + ' does not exist!')


    # Get the needed paths
    prjPath = replaceFileExtension(inputJp2Path, '.prj')
    vrtPath = replaceFileExtension(inputJp2Path, '.vrt')
    
    # The perl script works best from the input folder
    originalDirectory = os.getcwd()
    print originalDirectory
    inputFolder = os.path.dirname(inputJp2Path)
    print inputFolder
    if inputFolder != '':
        os.chdir(inputFolder)

    # Call perl script, then return to original directory
    cmd = 'isis3world.pl -J -prj ' + os.path.basename(inputHeaderPath)
    print cmd
    os.system(cmd)
    if inputFolder != '':
        os.chdir(originalDirectory)

    # If the first command is not called from the input folder, need to do this
    #mv .j2w <INPUT FOLDER>/B01_009838_2108_XI_30N319W.j2w
    #mv .prj <INPUT FOLDER>/B01_009838_2108_XI_30N319W.prj

    correctedProjPath = editProjFile(prjPath)

    if (not os.path.exists(correctedProjPath)):
        raise Exception('Failed to correct proj file!')
    
    # Determine the bounds of the image in projected coordinates
    projectedBounds = IrgGeoFunctions.getProjectedBoundsFromIsisLabel(inputHeaderPath)
    projectedBoundsString = (str(projectedBounds[0]) + ' ' +
                             str(projectedBounds[3]) + ' ' +
                             str(projectedBounds[1]) + ' ' +
                             str(projectedBounds[2]) )

    # Next conversion command
    #cmd = 'gdal_translate -of VRT -a_srs ESRI::"'+ prjPath +'" -a_nodata 0  '+ inputJp2Path +' '+ vrtPath

    ## Finish the conversion
    #cmd = 'gdal_translate -of JP2OpenJPEG '+ vrtPath +' '+ outputPath
    #print(cmd)
    #os.system(cmd)
    
    # Copy the input image to the output path if different
    if (outputPath != inputJp2Path):
        cmd = 'cp '+ inputJp2Path +'  '+ outputPath
        print(cmd)
        os.system(cmd)
    
    # Add metadata to the output image, specifying a projecting string and projected coordinate boundaries.
    f = open(correctedProjPath, 'r')
    prjText=f.read()
    f.close()
    cmd = 'gdal_edit.py -mo "AREA_OR_POINT=Area"  -a_ullr ' + projectedBoundsString + ' -a_srs "'+ prjText +'"  '+ outputPath
    print(cmd)
    os.system(cmd)

    # gdal_edit.py actually puts the metadata here, the input file is not touched!
    sidecarPath = outputPath + '.aux.xml'

    # Clean up temporary files
    if not keep:
        #os.remove(vrtPath)
        os.remove(prjPath)
        os.remove(correctedProjPath)

    return (outputPath, sidecarPath)
Exemple #22
0
def addGeoDataToAsuJp2File(inputJp2Path,
                           inputHeaderPath,
                           outputPath,
                           keep=False):
    """Does the actual work of adding the geo data"""

    if not os.path.exists(inputJp2Path):
        raise Exception('Input file ' + inputJp2Path + ' does not exist!')
    if not os.path.exists(inputHeaderPath):
        raise Exception('Input file ' + inputHeaderPath + ' does not exist!')

    # Get the needed paths
    prjPath = replaceFileExtension(inputJp2Path, '.prj')
    vrtPath = replaceFileExtension(inputJp2Path, '.vrt')

    # The perl script works best from the input folder
    originalDirectory = os.getcwd()
    print originalDirectory
    inputFolder = os.path.dirname(inputJp2Path)
    print inputFolder
    if inputFolder != '':
        os.chdir(inputFolder)

    # Call perl script, then return to original directory
    cmd = 'isis3world.pl -J -prj ' + os.path.basename(inputHeaderPath)
    print cmd
    os.system(cmd)
    if inputFolder != '':
        os.chdir(originalDirectory)

    # If the first command is not called from the input folder, need to do this
    #mv .j2w <INPUT FOLDER>/B01_009838_2108_XI_30N319W.j2w
    #mv .prj <INPUT FOLDER>/B01_009838_2108_XI_30N319W.prj

    correctedProjPath = editProjFile(prjPath)

    if (not os.path.exists(correctedProjPath)):
        raise Exception('Failed to correct proj file!')

    # Determine the bounds of the image in projected coordinates
    projectedBounds = IrgGeoFunctions.getProjectedBoundsFromIsisLabel(
        inputHeaderPath)
    projectedBoundsString = (str(projectedBounds[0]) + ' ' +
                             str(projectedBounds[3]) + ' ' +
                             str(projectedBounds[1]) + ' ' +
                             str(projectedBounds[2]))

    # Next conversion command
    #cmd = 'gdal_translate -of VRT -a_srs ESRI::"'+ prjPath +'" -a_nodata 0  '+ inputJp2Path +' '+ vrtPath

    ## Finish the conversion
    #cmd = 'gdal_translate -of JP2OpenJPEG '+ vrtPath +' '+ outputPath
    #print(cmd)
    #os.system(cmd)

    # Copy the input image to the output path if different
    if (outputPath != inputJp2Path):
        cmd = 'cp ' + inputJp2Path + '  ' + outputPath
        print(cmd)
        os.system(cmd)

    # Add metadata to the output image, specifying a projecting string and projected coordinate boundaries.
    f = open(correctedProjPath, 'r')
    prjText = f.read()
    f.close()
    cmd = 'gdal_edit.py -mo "AREA_OR_POINT=Area"  -a_ullr ' + projectedBoundsString + ' -a_srs "' + prjText + '"  ' + outputPath
    print(cmd)
    os.system(cmd)

    # gdal_edit.py actually puts the metadata here, the input file is not touched!
    sidecarPath = outputPath + '.aux.xml'

    # Clean up temporary files
    if not keep:
        #os.remove(vrtPath)
        os.remove(prjPath)
        os.remove(correctedProjPath)

    return (outputPath, sidecarPath)
Exemple #23
0
def qualityGdalwarp(imagePath, outputPath, imagePoints, gdcPoints):
    '''Use some workarounds to get a higher quality gdalwarp output than is normally possible.'''

    # Generate a high resolution grid of fake GCPs based on a transform we compute,
    # then call gdalwarp using a high order polynomial to accurately match our transform.

    #trans = transform.ProjectiveTransform.fit(numpy.asarray(gdcPoints),numpy.asarray(imagePoints))ls 
    trans = transform.getTransform(numpy.asarray(gdcPoints),numpy.asarray(imagePoints))
    transformName = trans.getJsonDict()['type']
    
    tempPath = outputPath + '-temp.tif'
    
    # Generate a temporary image containing the grid of fake GCPs
    cmd = ('gdal_translate -co "COMPRESS=LZW" -co "tiled=yes"  -co "predictor=2" -a_srs "'
           + OUTPUT_PROJECTION +'" '+ imagePath +' '+ tempPath)
    
    # Generate the GCPs in a grid, keeping the total under about 500 points so
    # that GDAL does not complain.
    (width, height) = IrgGeoFunctions.getImageSize(imagePath)
    xStep = width /22
    yStep = height/22
    MAX_DEG_SIZE = 20
    minLon = 999 # Keep track of the lonlat size and don't write if it is too big.
    minLat = 999 # - This would work better if it was in pixels, but how to get that size?
    maxLon = -999
    maxLat = -999
    for r in range(0,height,yStep):
        for c in range(0,width,xStep):
            pixel  = (c,r)
            lonlat = trans.forward(pixel)
            cmd += ' -gcp '+ str(c) +' '+str(r) +' '+str(lonlat[0]) +' '+str(lonlat[1])
            if lonlat[0] < minLon:
                minLon = lonlat[0]
            if lonlat[1] < minLat:
                minLat = lonlat[1]
            if lonlat[0] > maxLon:
                maxLon = lonlat[0]
            if lonlat[1] > maxLat:
                maxLat = lonlat[1]
    #print cmd
    os.system(cmd)
    if max((maxLon - minLon), (maxLat - minLat)) > MAX_DEG_SIZE:
        raise Exception('Warped image is too large to generate!\n'
                        '-> LonLat bounds: ' + str((minLon, minLat, maxLon, maxLat)))

    # Now generate a warped geotiff.
    # - "order 2" looks terrible with fewer GCPs, but "order 1" does not accurately
    #   capture the footprint of higher tilt images.
    # - tps seems to work well with the evenly spaced grid of virtual GCPs.
    cmd = ('gdalwarp -co "COMPRESS=LZW" -co "tiled=yes"  -co "predictor=2"'
               + ' -dstalpha -overwrite -tps -multi -r cubic -t_srs "'
           + OUTPUT_PROJECTION +'" ' + tempPath +' '+ outputPath)
    print cmd
    os.system(cmd)

    # Check output and cleanup
    os.remove(tempPath)
    if not os.path.exists(outputPath):
        raise Exception('Failed to create warped geotiff file: ' + outputPath)

    return transformName
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
def fetchAndPrepFile(db, setName, subtype, remoteURL, workDir):
    '''Retrieves a remote file and prepares it for upload'''

    #print 'Uploading file ' + setName

    # Images with a center over this latitude will use polar stereographic projection
    #   instead of simple cylindrical projection.
    HIGH_LATITUDE_CUTOFF = 65  # Degrees

    asuImagePath = os.path.join(workDir, setName +
                                '_noGeo.jp2')  # Map projected image from ASU
    asuLabelPath = os.path.join(workDir,
                                setName + '_noGeo.lbl')  # Label from ASU
    edrPath = os.path.join(workDir, setName + '.IMG')  # Raw image from PDS
    timePath = os.path.join(
        workDir,
        setName + '.time')  # Contains only the file capture time string
    #cubPath     = os.path.join(workDir, setName + '.cub')        # Output of mroctx2isis
    #calPath     = os.path.join(workDir, setName + '.cal.cub')    # Output of ctxcal
    mapPath = os.path.join(workDir, setName + '.map.cub')  # Output of cam2map
    mapLabelPath = os.path.join(workDir, setName +
                                '.map.pvl')  # Specify projection to cam2map

    # Generate the remote URLs from the data prefix and volume stored in these parameters
    asuImageUrl, asuLabelUrl, edrUrl = generatePdsPath(setName, subtype)

    if True:  # Map project the EDR ourselves <-- This takes way too long!
        print 'Projecting the EDR image using ISIS...'

        localFilePath = os.path.join(workDir, setName +
                                     '.tif')  # The output file we will upload

        # Check if this is a "flat" calibration image
        badImage = checkForBadFile(edrUrl)
        if badImage:
            raise Exception('TODO: Remove bad images from the DB!')

        if not os.path.exists(edrPath):
            # Download the EDR file
            cmd = 'wget ' + edrUrl + ' -O ' + edrPath
            print cmd
            os.system(cmd)

        # Extract the image capture time from the .IMG file
        if not os.path.exists(timePath):
            timeString = getCreationTimeHelper(edrPath)
            f = open(timePath, 'w')
            f.write(timeString)
            f.close()

        # Convert and apply calibration to the CTX file
        calPath = IrgIsisFunctions.prepareCtxImage(edrPath, workDir, False)

        ## Find out the center latitude of the file and determine if it is high latitude
        centerLat = IrgIsisFunctions.getCubeCenterLatitude(calPath, workDir)
        print centerLat
        highLat = abs(float(centerLat)) > HIGH_LATITUDE_CUTOFF

        if True:  #not os.path.exists(mapLabelPath):
            # Generate the map label file
            generateDefaultMappingPvl(mapLabelPath, highLat)

        if True:  #not os.path.exists(mapPath):
            # Generate the map projected file
            cmd = [
                'timeout', '6h', 'cam2map', 'matchmap=', 'False', 'from=',
                calPath, 'to=', mapPath, 'map=', mapLabelPath
            ]
            print cmd
            #os.system(cmd)
            p = subprocess.Popen(cmd)
            p.communicate()
            if (p.returncode != 0):
                raise Exception(
                    'Error or timeout running cam2map, returnCode = ' +
                    str(p.returncode))

        if True:  #not os.path.exists(localFilePath):
            # Generate the final image to upload
            cmd = 'gdal_translate -of GTiff ' + mapPath + ' ' + localFilePath
            print cmd
            os.system(cmd)

        # Clean up intermediate files
        os.remove(mapLabelPath)
        os.remove(edrPath)
        os.remove(calPath)
        os.remove(mapPath)

        # Two local files are left around, the first should be uploaded.
        return [localFilePath, timePath]

    else:  # Use the map projected image from the ASU web site
        print 'Using ASU projected image...'

        localFilePath = os.path.join(workDir, setName +
                                     '.jp2')  # The output file we will upload

        # Note: ASU seems to be missing some files!
        # We are using the label path in both projection cases
        if not os.path.exists(asuLabelPath):
            # Download the label file
            cmd = 'wget "' + asuLabelUrl + '" -O ' + asuLabelPath
            print cmd
            os.system(cmd)
        if not IrgFileFunctions.fileIsNonZero(
                asuLabelPath):  # Try the alternate label path
            os.remove(asuLabelPath)
            asuLabelUrl = asuLabelUrl.replace('.scyl.', '.ps.')
            asuLabelPath = asuLabelPath.replace('.scyl.', '.ps.')
            print 'Trying alternate label path: ' + asuLabelUrl
            # Download the label file
            cmd = 'wget "' + asuLabelUrl + '" -O ' + asuLabelPath
            print cmd
            os.system(cmd)
            if not IrgFileFunctions.fileIsNonZero(asuLabelPath):
                raise Exception('Failed to download file label at URL: ' +
                                asuLabelUrl)

        # Check the projection type
        projType = IrgGeoFunctions.getProjectionFromIsisLabel(asuLabelPath)
        if projType != 'SimpleCylindrical':
            print 'WARNING: projType = ' + projType
            print 'Maps Engine may fail to ingest this file!'
            #os.remove(asuLabelPath)
            #raise Exception(projType + ' images on hold until Google fixes a bug!')

        if not os.path.exists(asuImagePath):
            # Download the image file
            cmd = 'wget "' + asuImageUrl + '" -O ' + asuImagePath
            print cmd
            os.system(cmd)
        if not IrgFileFunctions.fileIsNonZero(asuImagePath):
            raise Exception('Failed to download image file at URL: ' +
                            asuImageUrl)

        ## Correct the ISIS header if needed
        #fixedAsuHeaderPath = putIsisHeaderIn180(asuLabelPath)
        #if (fixedAsuHeaderPath != asuLabelPath):
        #    os.remove(asuLabelPath) # Delete replaced header

        if True:  # This is fast now, so do it every time
            # Correct the file - The JP2 file from ASU needs the geo data from the label file!
            #cmd = 'addGeoToAsuCtxJp2.py --keep --label '+ asuLabelPath +' '+ asuImagePath +' '+ localFilePath
            #print cmd
            #os.system(cmd)
            # TODO: Remove unnecessary image copy here
            (correctedPath,
             sidecarPath) = addGeoDataToAsuJp2File(asuImagePath,
                                                   asuLabelPath,
                                                   localFilePath,
                                                   keep=False)

            if not IrgFileFunctions.fileIsNonZero(sidecarPath):
                raise Exception('Script to add geo data to JP2 file failed!')

        # Clean up
        os.remove(asuImagePath)
        # Three local files are left around, the first should be uploaded.
        return [correctedPath, sidecarPath, asuLabelPath]
def fetchAndPrepFile(db, setName, subtype, remoteURL, workDir):
    '''Retrieves a remote file and prepares it for upload'''

    #print 'Uploading file ' + setName

    if subtype != 'DEM':  # Handles RED and COLOR images
        # The label file URL is the same as the image but with a different extension
        remoteLabelURL = getLabelPathFromImagePath(remoteURL)

        localFilePath = os.path.join(workDir, os.path.basename(remoteURL))
        localLabelPath = os.path.join(workDir,
                                      os.path.basename(remoteLabelURL))

        # Retrieve the header file
        if not os.path.exists(localLabelPath):
            # Try to get the label locally!
            pdsStart = remoteLabelURL.find('PDS')
            localPdsPath = os.path.join('/HiRISE/Data/',
                                        remoteLabelURL[pdsStart:])
            print localPdsPath
            if os.path.exists(
                    localPdsPath):  # File available locally, just copy it!
                cmd = 'cp ' + localPdsPath + ' ' + localLabelPath
            else:  # Download the image
                cmd = 'wget ' + remoteLabelURL + ' -O ' + localLabelPath
            print cmd
            os.system(cmd)

        # Retrieve the image file
        if not os.path.exists(localFilePath):  # Need to get it from somewhere
            # Try to get the image locally!
            pdsStart = remoteURL.find('PDS')
            localPdsPath = os.path.join('/HiRISE/Data/', remoteURL[pdsStart:])
            if os.path.exists(
                    localPdsPath):  # File available locally, just copy it!
                cmd = 'cp ' + localPdsPath + ' ' + localFilePath
            else:  # Download the image
                cmd = 'wget ' + remoteURL + ' -O ' + localFilePath
            print cmd
            os.system(cmd)

        if not IrgFileFunctions.fileIsNonZero(localFilePath):
            if not IrgFileFunctions.fileIsNonZero(localLabelPath):
                print 'Could not find label or image file, DELETING DATA RECORD!'
                common.removeDataRecord(db, common.SENSOR_TYPE_HiRISE, subtype,
                                        setName)
            raise Exception('Unable to download from URL: ' + remoteURL)

        # Check if there is geo data in the JP2 file
        jp2HasGeoData = IrgGeoFunctions.doesImageHaveGeoData(localFilePath)

        # If there is no label file, try to generate an artificial one.
        fakeLabelFile = False
        if not IrgFileFunctions.fileIsNonZero(localLabelPath):
            #raise Exception('Unable to download from URL: ' + remoteLabelURL)
            print 'WARNING: Unable to download from URL: ' + remoteLabelURL
            if not jp2HasGeoData:
                raise Exception(
                    'No geo data in JP2 and no Label file!  Cannot handle this!'
                )
            print 'Generating a fake LBL file to proceed...'
            localLabelPath = writeFakeLabelFromJp2(localFilePath)
            fakeLabelFile = True
        # At this point we always have a label file but it may be fake

        # Some images are missing geo information but we can add it from the label file
        localImageIsTiff = False
        localImagePath = localFilePath
        if not jp2HasGeoData:
            print 'Correcting JP2 file with no geo information!!!!'
            # Correct the local file, then remove the old (bad) file
            outputPrefix = localFilePath[0:-4]
            localImagePath = correctAndCropImage(localFilePath, localLabelPath,
                                                 outputPrefix)
            print 'Generated ' + localImagePath
            if localFilePath != localImagePath:
                print 'Deleting JP2 file without metadata!'
                os.remove(localFilePath)
            localImageIsTiff = (localImagePath[-4:] == ".TIF")

        if not localImageIsTiff:
            # Call code to fix the header information in the JP2 file!
            cmd = FIX_JP2_TOOL + ' ' + localImagePath
            print cmd
            os.system(cmd)

        # Check the projection type
        projType = IrgGeoFunctions.getProjectionFromIsisLabel(localLabelPath)
        (width, height) = IrgIsisFunctions.getImageSize(localImagePath)
        if (projType == 'POLAR STEREOGRAPHIC'
            ) and False:  #(width < MAX_POLAR_UPLOAD_WIDTH):
            # Google has trouble digesting these files so handle them differently.

            #os.remove(localLabelPath)
            #raise Exception('POLAR STEREOGRAPHIC images on hold until Google fixes a bug!')
            print 'Special handling for POLAR STEROGRAPHIC image!'

            if fakeLabelFile:
                print 'Cannot reprocess polar image without a label file!'
                print 'All we can do is upload the file and hope for the best.'
                # First file is for upload, second contains the timestamp.
                return [localImagePath, localLabelPath]

            # Compute how many chunks are needed for this image
            numChunks = ceil(width / POLAR_WIDTH_CHUNK_SIZE)

            # Determine which chunk this DB entry is for
            chunkNum = getChunkNum(setName)
            print 'This is chunk number ' + str(chunkNum)

            if chunkNum >= numChunks:  # Check for chunk number error
                raise Exception('Illegal chunk number: ' + setName)

            # If this is the main DB entry, we need to make sure the other DB entries exist!
            if chunkNum == 0:
                # Go ahead and try to add each chunk, the call will only go through if it does not already exist.
                for i in range(1, numChunks):
                    chunkSetName = makeChunkSetName(setName, i)
                    print 'Add chunk set name to DB: ' + chunkSetName
                    common.addDataRecord(db, common.SENSOR_TYPE_HiRISE,
                                         subtype, chunkSetName, remoteURL)

            raise Exception('DEBUG')

            # Now actually generate the desired chunk
            # - Need to use PIRL tools to extract a chunk to an IMG format, then convert that back to JP2 so that Google can read it.
            fileBasePath = os.path.splitext(localImagePath)[0]
            localImgPath = fileBasePath + '.IMG'
            localChunkPrefix = fileBasePath + '_' + str(chunkNum)
            chunkBB = getChunkBoundingBox(width, height, chunkNum)
            localChunkPath = correctAndCropImage(localImagePath,
                                                 localLabelPath,
                                                 localChunkPrefix, chunkBB)

            # Just use the same label file, we don't care if the DB has per-chunk boundaries.
            return [localChunkPath, localLabelPath]

        else:  # A normal, non-polar file.
            # First file is for upload, second contains the timestamp.
            return [localImagePath, localLabelPath]

    # TODO: Handle POLAR DEMS

    else:  # Handle DEMs

        # For DEMs there is no label file
        localFilePath = os.path.join(workDir, os.path.basename(remoteURL))
        if not os.path.exists(localFilePath):
            # Download the image
            cmd = 'wget ' + remoteURL + ' -O ' + localFilePath
            print cmd
            os.system(cmd)
        if not IrgFileFunctions.fileIsNonZero(
                localFilePath):  # Make sure we got the file
            raise Exception('Unable to download from URL: ' + remoteURL)

        # Generate a header file from the IMG file
        localLabelPath = localFilePath[:-4] + '.LBL'
        cmd = 'head -n 90 ' + localFilePath + ' >  ' + localLabelPath
        print cmd
        os.system(cmd)

        # Check if this is a polar stereographic image
        isPolar = False
        f = open(localLabelPath)
        for line in f:
            if ("MAP_PROJECTION_TYPE" in line) and ("POLAR STEREOGRAPHIC"
                                                    in line):
                isPolar = True
                print 'WARNING: POLAR STEREOGRAPHIC DEM MAY NOT UPLOAD PROPERLY'
                break
                #os.remove(localFilePath)
                #os.remove(localLabelPath)
                #raise Exception('POLAR STEREOGRAPHIC DEMs on hold until Google fixes a bug!')
        f.close()

        # Convert from IMG to TIF
        tiffFilePath = localFilePath[:-4] + '.TIF'
        if not os.path.exists(tiffFilePath):
            cmd = 'gdal_translate -of GTiff ' + localFilePath + ' ' + tiffFilePath
            print cmd
            os.system(cmd)

            if not isPolar:  # Only EQC files need to be corrected
                # Correct projected coordinates problems
                cmd = 'python /home/pirl/smcmich1/repo/Tools/geoTiffTool.py --normalize-eqc-lon ' + tiffFilePath
                print cmd
                os.system(cmd)

        os.remove(localFilePath)  # Clean up source image
        return [tiffFilePath, localLabelPath]
def matchLocally(mission, roll, frame, cursor, georefDb, sourceImagePath):
    '''Performs image alignment to an already aligned ISS image'''

    # Load new frame info
    targetFrameData = source_database.FrameInfo()
    targetFrameData.loadFromDb(cursor, mission, roll, frame)
    targetFrameData = computeFrameInfoMetersPerPixel(targetFrameData)

    # Find candidate names to match to
    possibleNearbyMatches = findNearbyResults(targetFrameData, cursor, georefDb)
        
    if not possibleNearbyMatches:
        print 'Did not find any potential local matches!'
    
    for (otherFrame, ourResult) in possibleNearbyMatches:

        print 'Trying local match with frame: ' + str(otherFrame.frame)
        
        # Get path to other frame image
        otherImagePath, exifSourcePath = source_database.getSourceImage(otherFrame)
        source_database.clearExif(exifSourcePath)
        otherTransform = ourResult[0] # This is still in the google projected format
        
        #print 'otherTransform = ' + str(otherTransform.matrix)
        
        print 'New image mpp = ' + str(targetFrameData.metersPerPixel)
        print 'Local match image mpp = ' + str(otherFrame.metersPerPixel)
        # If we could not estimate the MPP value of the new image, guess that it is the same as
        #  the local reference image we are about to try.
        thisMpp = targetFrameData.metersPerPixel
        if not thisMpp:
            thisMpp = otherFrame.metersPerPixel
        
        print 'Attempting to register image...'
        (imageToProjectedTransform, imageToGdcTransform, confidence, imageInliers, gdcInliers, refMetersPerPixel) = \
            register_image.register_image(sourceImagePath,
                                          otherFrame.centerLon, otherFrame.centerLat,
                                          thisMpp, targetFrameData.date,
                                          refImagePath         =otherImagePath,
                                          referenceGeoTransform=otherTransform,
                                          refMetersPerPixelIn  =otherFrame.metersPerPixel,
                                          debug=options.debug, force=True, slowMethod=False)       
        if not options.debug:
            os.remove(otherImagePath) # Clean up the image we matched against

        # Quit once we get a good match
        if confidence == registration_common.CONFIDENCE_HIGH:
            print 'High confidence match!'
            # Convert from the image-to-image GCPs to the reference image GCPs
            #  located in the new image.
            refFrameGdcInliers = ourResult[3] # TODO: Clean this up!
            (width, height)    = IrgGeoFunctions.getImageSize(sourceImagePath)
            
            print '\n\n'
            print refFrameGdcInliers
            print '\n\n'
            
            (imageInliers, gdcInliers) = registration_common.convertGcps(refFrameGdcInliers,
                                                imageToProjectedTransform, width, height)
            
            print imageInliers
            print '\n\n'
            
            # If none of the original GCPs fall in the new image, don't use this alignment result.
            # - We could use this result, but we don't in order to maintain accuracy standards.
            if imageInliers:
                print 'Have inliers'
                print otherFrame
                return (imageToProjectedTransform, imageToGdcTransform, confidence,
                        imageInliers, gdcInliers, refMetersPerPixel, otherFrame)
            else:
                print 'Inliers out of bounds!'

    # Match failure, return junk values
    return (registration_common.getIdentityTransform(), registration_common.getIdentityTransform(),
            registration_common.CONFIDENCE_NONE, [], [], 9999, None)
def getBoundingBox(fileList):
    """Return the bounding box for this data set in the format (minLon, maxLon, minLat, maxLat)"""
    if len(fileList) == 2: # Read BB from the label file
        return IrgGeoFunctions.getBoundingBoxFromIsisLabel(fileList[1])
    else: # No label file, read it from the the main file
        return IrgGeoFunctions.getImageBoundingBox(fileList[0]) # This information is also available in the IMG file header
def fetchAndPrepFile(db, setName, subtype, remoteURL, workDir):
    '''Retrieves a remote file and prepares it for upload'''
    
    #print 'Uploading file ' + setName
  
    if subtype != 'DEM': # Handles RED and COLOR images
        # The label file URL is the same as the image but with a different extension
        remoteLabelURL = getLabelPathFromImagePath(remoteURL)
    
        localFilePath  = os.path.join(workDir, os.path.basename(remoteURL))
        localLabelPath = os.path.join(workDir, os.path.basename(remoteLabelURL))

        # Retrieve the header file
        if not os.path.exists(localLabelPath):
            # Try to get the label locally!
            pdsStart     = remoteLabelURL.find('PDS')
            localPdsPath = os.path.join('/HiRISE/Data/', remoteLabelURL[pdsStart:])
            print localPdsPath
            if os.path.exists(localPdsPath): # File available locally, just copy it!
                cmd = 'cp ' + localPdsPath +' '+ localLabelPath
            else:  # Download the image
                cmd = 'wget ' + remoteLabelURL + ' -O ' + localLabelPath
            print cmd
            os.system(cmd)

        # Retrieve the image file
        if not os.path.exists(localFilePath): # Need to get it from somewhere
            # Try to get the image locally!
            pdsStart     = remoteURL.find('PDS')
            localPdsPath = os.path.join('/HiRISE/Data/', remoteURL[pdsStart:])
            if os.path.exists(localPdsPath): # File available locally, just copy it!
                cmd = 'cp ' + localPdsPath +' '+ localFilePath
            else:  # Download the image
                cmd = 'wget ' + remoteURL + ' -O ' + localFilePath
            print cmd
            os.system(cmd)

        if not IrgFileFunctions.fileIsNonZero(localFilePath):
            if not IrgFileFunctions.fileIsNonZero(localLabelPath):
                print 'Could not find label or image file, DELETING DATA RECORD!'
                common.removeDataRecord(db, common.SENSOR_TYPE_HiRISE, subtype, setName)
            raise Exception('Unable to download from URL: ' + remoteURL)

        # Check if there is geo data in the JP2 file
        jp2HasGeoData = IrgGeoFunctions.doesImageHaveGeoData(localFilePath)

        # If there is no label file, try to generate an artificial one.
        fakeLabelFile = False
        if not IrgFileFunctions.fileIsNonZero(localLabelPath):
            #raise Exception('Unable to download from URL: ' + remoteLabelURL)
            print 'WARNING: Unable to download from URL: ' + remoteLabelURL
            if not jp2HasGeoData:
                raise Exception('No geo data in JP2 and no Label file!  Cannot handle this!')
            print 'Generating a fake LBL file to proceed...'
            localLabelPath = writeFakeLabelFromJp2(localFilePath)
            fakeLabelFile  = True
        # At this point we always have a label file but it may be fake

        # Some images are missing geo information but we can add it from the label file
        localImageIsTiff = False
        localImagePath   = localFilePath
        if not jp2HasGeoData:
            print 'Correcting JP2 file with no geo information!!!!'
            # Correct the local file, then remove the old (bad) file
            outputPrefix   = localFilePath[0:-4]
            localImagePath = correctAndCropImage(localFilePath, localLabelPath, outputPrefix)
            print 'Generated ' + localImagePath
            if localFilePath != localImagePath:
                print 'Deleting JP2 file without metadata!'
                os.remove(localFilePath)
            localImageIsTiff = (localImagePath[-4:] == ".TIF")
    
        if not localImageIsTiff:
            # Call code to fix the header information in the JP2 file!
            cmd = FIX_JP2_TOOL +' '+ localImagePath
            print cmd
            os.system(cmd)


        # Check the projection type
        projType        = IrgGeoFunctions.getProjectionFromIsisLabel(localLabelPath)
        (width, height) = IrgIsisFunctions.getImageSize(localImagePath)
        if (projType == 'POLAR STEREOGRAPHIC') and False: #(width < MAX_POLAR_UPLOAD_WIDTH):
            # Google has trouble digesting these files so handle them differently.

            #os.remove(localLabelPath)
            #raise Exception('POLAR STEREOGRAPHIC images on hold until Google fixes a bug!')
            print 'Special handling for POLAR STEROGRAPHIC image!'

            if fakeLabelFile:
                print 'Cannot reprocess polar image without a label file!'
                print 'All we can do is upload the file and hope for the best.'
                # First file is for upload, second contains the timestamp.
                return [localImagePath, localLabelPath]
            
            # Compute how many chunks are needed for this image
            numChunks = ceil(width / POLAR_WIDTH_CHUNK_SIZE)
            
            # Determine which chunk this DB entry is for
            chunkNum = getChunkNum(setName)
            print 'This is chunk number ' + str(chunkNum)
            
            if chunkNum >= numChunks: # Check for chunk number error
                raise Exception('Illegal chunk number: ' + setName)
                
            # If this is the main DB entry, we need to make sure the other DB entries exist!
            if chunkNum == 0:
                # Go ahead and try to add each chunk, the call will only go through if it does not already exist.
                for i in range(1,numChunks):
                    chunkSetName = makeChunkSetName(setName, i)
                    print 'Add chunk set name to DB: ' + chunkSetName
                    common.addDataRecord(db, common.SENSOR_TYPE_HiRISE, subtype, chunkSetName, remoteURL)
                    
            raise Exception('DEBUG')
            
            # Now actually generate the desired chunk
            # - Need to use PIRL tools to extract a chunk to an IMG format, then convert that back to JP2 so that Google can read it.
            fileBasePath     = os.path.splitext(localImagePath)[0]
            localImgPath     = fileBasePath + '.IMG'
            localChunkPrefix = fileBasePath + '_' + str(chunkNum)
            chunkBB = getChunkBoundingBox(width, height, chunkNum)
            localChunkPath = correctAndCropImage(localImagePath, localLabelPath, localChunkPrefix, chunkBB)           
            
            # Just use the same label file, we don't care if the DB has per-chunk boundaries.
            return [localChunkPath, localLabelPath]
            
        else: # A normal, non-polar file.
            # First file is for upload, second contains the timestamp.
            return [localImagePath, localLabelPath]

    
    # TODO: Handle POLAR DEMS

    else: # Handle DEMs
        
        # For DEMs there is no label file
        localFilePath = os.path.join(workDir, os.path.basename(remoteURL))
        if not os.path.exists(localFilePath):
            # Download the image
            cmd = 'wget ' + remoteURL + ' -O ' + localFilePath
            print cmd
            os.system(cmd)
        if not IrgFileFunctions.fileIsNonZero(localFilePath): # Make sure we got the file
            raise Exception('Unable to download from URL: ' + remoteURL)

        # Generate a header file from the IMG file
        localLabelPath = localFilePath[:-4] + '.LBL'
        cmd = 'head -n 90 ' + localFilePath +' >  '+ localLabelPath
        print cmd
        os.system(cmd)

        # Check if this is a polar stereographic image
        isPolar = False 
        f = open(localLabelPath)
        for line in f:
            if ("MAP_PROJECTION_TYPE" in line) and ("POLAR STEREOGRAPHIC" in line):
                isPolar = True
                print 'WARNING: POLAR STEREOGRAPHIC DEM MAY NOT UPLOAD PROPERLY'
                break
                #os.remove(localFilePath)
                #os.remove(localLabelPath)
                #raise Exception('POLAR STEREOGRAPHIC DEMs on hold until Google fixes a bug!')
        f.close()
            
        # Convert from IMG to TIF
        tiffFilePath = localFilePath[:-4] + '.TIF'
        if not os.path.exists(tiffFilePath):
            cmd = 'gdal_translate -of GTiff ' + localFilePath +' '+ tiffFilePath
            print cmd
            os.system(cmd)
        
            if not isPolar: # Only EQC files need to be corrected
                # Correct projected coordinates problems
                cmd = 'python /home/pirl/smcmich1/repo/Tools/geoTiffTool.py --normalize-eqc-lon ' + tiffFilePath
                print cmd
                os.system(cmd)

        os.remove(localFilePath) # Clean up source image
        return [tiffFilePath, localLabelPath] 
def fetchAndPrepFile(db, setName, subtype, remoteURL, workDir):
    '''Retrieves a remote file and prepares it for upload'''
    
    #print 'Uploading file ' + setName
    
    # Images with a center over this latitude will use polar stereographic projection
    #   instead of simple cylindrical projection.
    HIGH_LATITUDE_CUTOFF = 65 # Degrees
    
    asuImagePath  = os.path.join(workDir, setName + '_noGeo.jp2') # Map projected image from ASU
    asuLabelPath  = os.path.join(workDir, setName + '_noGeo.lbl') # Label from ASU
    edrPath       = os.path.join(workDir, setName + '.IMG')       # Raw image from PDS
    timePath      = os.path.join(workDir, setName + '.time')      # Contains only the file capture time string
    #cubPath     = os.path.join(workDir, setName + '.cub')        # Output of mroctx2isis
    #calPath     = os.path.join(workDir, setName + '.cal.cub')    # Output of ctxcal
    mapPath       = os.path.join(workDir, setName + '.map.cub')   # Output of cam2map
    mapLabelPath  = os.path.join(workDir, setName + '.map.pvl')   # Specify projection to cam2map
    
    # Generate the remote URLs from the data prefix and volume stored in these parameters
    asuImageUrl, asuLabelUrl, edrUrl = generatePdsPath(setName, subtype)
    
    if True: # Map project the EDR ourselves <-- This takes way too long!
        print 'Projecting the EDR image using ISIS...'

        localFilePath = os.path.join(workDir, setName + '.tif') # The output file we will upload

        # Check if this is a "flat" calibration image
        badImage = checkForBadFile(edrUrl)       
        if badImage:
            raise Exception('TODO: Remove bad images from the DB!')
        
        if not os.path.exists(edrPath):
            # Download the EDR file
            cmd = 'wget ' + edrUrl + ' -O ' + edrPath
            print cmd
            os.system(cmd)

        # Extract the image capture time from the .IMG file
        if not os.path.exists(timePath):
            timeString = getCreationTimeHelper(edrPath)
            f = open(timePath, 'w')
            f.write(timeString)
            f.close()
      
        # Convert and apply calibration to the CTX file
        calPath = IrgIsisFunctions.prepareCtxImage(edrPath, workDir, False)

        ## Find out the center latitude of the file and determine if it is high latitude
        centerLat = IrgIsisFunctions.getCubeCenterLatitude(calPath, workDir)
        print centerLat
        highLat   = abs(float(centerLat)) > HIGH_LATITUDE_CUTOFF

        if True:#not os.path.exists(mapLabelPath):
            # Generate the map label file           
            generateDefaultMappingPvl(mapLabelPath, highLat)
        
        
        if True:#not os.path.exists(mapPath):
            # Generate the map projected file
            cmd = ['timeout', '6h', 'cam2map', 'matchmap=','False', 'from=', calPath, 'to=', mapPath, 'map=', mapLabelPath]
            print cmd
            #os.system(cmd)
            p = subprocess.Popen(cmd)
            p.communicate()
            if (p.returncode != 0):
                raise Exception('Error or timeout running cam2map, returnCode = ' + str(p.returncode))
       
        if True: #not os.path.exists(localFilePath):
            # Generate the final image to upload
            cmd = 'gdal_translate -of GTiff ' + mapPath + ' ' + localFilePath
            print cmd
            os.system(cmd)
        
        # Clean up intermediate files    
        os.remove(mapLabelPath)
        os.remove(edrPath)
        os.remove(calPath)
        os.remove(mapPath)
        
        # Two local files are left around, the first should be uploaded.
        return [localFilePath, timePath]
        
    else: # Use the map projected image from the ASU web site
        print 'Using ASU projected image...'
        
        localFilePath = os.path.join(workDir, setName + '.jp2')  # The output file we will upload
        
        # Note: ASU seems to be missing some files!
        # We are using the label path in both projection cases
        if not os.path.exists(asuLabelPath):
            # Download the label file
            cmd = 'wget "' + asuLabelUrl + '" -O ' + asuLabelPath
            print cmd
            os.system(cmd)
        if not IrgFileFunctions.fileIsNonZero(asuLabelPath): # Try the alternate label path
            os.remove(asuLabelPath)
            asuLabelUrl  = asuLabelUrl.replace( '.scyl.', '.ps.')
            asuLabelPath = asuLabelPath.replace('.scyl.', '.ps.')
            print 'Trying alternate label path: ' + asuLabelUrl
            # Download the label file
            cmd = 'wget "' + asuLabelUrl + '" -O ' + asuLabelPath
            print cmd
            os.system(cmd)
            if not IrgFileFunctions.fileIsNonZero(asuLabelPath):
                raise Exception('Failed to download file label at URL: ' + asuLabelUrl)
        
        # Check the projection type
        projType = IrgGeoFunctions.getProjectionFromIsisLabel(asuLabelPath)
        if projType != 'SimpleCylindrical':
            print 'WARNING: projType = ' + projType
            print 'Maps Engine may fail to ingest this file!'
            #os.remove(asuLabelPath)
            #raise Exception(projType + ' images on hold until Google fixes a bug!')
        
        if not os.path.exists(asuImagePath):
            # Download the image file
            cmd = 'wget "' + asuImageUrl + '" -O ' + asuImagePath
            print cmd
            os.system(cmd)
        if not IrgFileFunctions.fileIsNonZero(asuImagePath):
            raise Exception('Failed to download image file at URL: ' + asuImageUrl)

        ## Correct the ISIS header if needed
        #fixedAsuHeaderPath = putIsisHeaderIn180(asuLabelPath)
        #if (fixedAsuHeaderPath != asuLabelPath):
        #    os.remove(asuLabelPath) # Delete replaced header

        if True: # This is fast now, so do it every time
            # Correct the file - The JP2 file from ASU needs the geo data from the label file!
            #cmd = 'addGeoToAsuCtxJp2.py --keep --label '+ asuLabelPath +' '+ asuImagePath +' '+ localFilePath
            #print cmd
            #os.system(cmd)
            # TODO: Remove unnecessary image copy here
            (correctedPath, sidecarPath) = addGeoDataToAsuJp2File(asuImagePath, asuLabelPath, localFilePath, keep=False)
            
            if not IrgFileFunctions.fileIsNonZero(sidecarPath):
                raise Exception('Script to add geo data to JP2 file failed!')
   
        # Clean up
        os.remove(asuImagePath)
        # Three local files are left around, the first should be uploaded.
        return [correctedPath, sidecarPath, asuLabelPath]
    def loadFrame(self, mission, roll, frame):
        '''Populate from an entry in the database'''
        self._dbCursor.execute('select * from Frames where trim(MISSION)=? and trim(ROLL)=? and trim(FRAME)=?',
                       (mission, roll, frame))
        rows = self._dbCursor.fetchall()
        if len(rows) != 1: # Make sure we found the next lines
            raise Exception('Could not find any data for frame: ' +
                            source_image_utils.getFrameString(mission, roll, frame))

        output = source_image_utils.FrameInfo()
        rows = rows[0]
        #print rows
        output.mission         = mission
        output.roll            = roll
        output.frame           = frame
        output.exposure        = str(rows[0]).strip()
        output.tilt            = str(rows[3]).strip()
        output.time            = str(rows[8]).strip()
        output.date            = str(rows[9]).strip()
        output.cloudPercentage = float(rows[13]) / 100
        output.altitude        = float(rows[15])
        output.focalLength     = float(rows[18])
        output.centerLat       = float(rows[19])
        output.centerLon       = float(rows[20])
        output.nadirLat        = float(rows[21])
        output.nadirLon        = float(rows[22])
        output.camera          = str(rows[23]).strip()
        output.film            = str(rows[24]).strip()
        if (output.centerLat) and (output.centerLon):
            output.centerPointSource = georefDbWrapper.AUTOWCENTER
        output.metersPerPixel  = None # This information is not stored in the database

        # Clean up the time format
        output.time = output.time[0:2] +':'+ output.time[2:4] +':'+ output.time[4:6]

        # The input tilt can be in letters or degrees so convert
        #  it so that it is always in degrees.
        if (output.tilt == 'NV') or not output.tilt:
            output.tilt = '0'
        if output.tilt == 'LO': # We want to try these
            output.tilt = '30'
        if output.tilt == 'HO': # Do not want to try these!
            output.tilt = '80'
        output.tilt = float(output.tilt)

        # Convert the date to 'YYYY.MM.DD' format that the image fetcher wants
        # - TODO: Use a standardized format!
        output.date = output.date[0:4] + '.' + output.date[4:6] + '.' +  output.date[6:8]

        # Get the sensor size
        (output.sensorWidth, output.sensorHeight) = \
          source_image_utils.getSensorSize(output.camera)

        #if not output.isGoodAlignmentCandidate():
        #    return # In this case don't bother finding the images

        # Fetch the associated non-raw image files
        dbCursor.execute('select * from Images where trim(MISSION)=? and trim(ROLL)=? and trim(FRAME)=?',
                           (mission, roll, frame))
        rows = dbCursor.fetchall()
        if len(rows) < 1: # No images provided
            return
        # Record the image paths
        bestNumPixels = 0
        for row in rows:
            # Get the file path and verify it exists
            folder = str(row[4]).strip()
            name   = str(row[5]).strip()
            path   = os.path.join(folder, name)
            if not os.path.exists(path):
                continue
            output.imageList.append(path)

            # Record if this is the highest resolution image
            width     = int(row[6])
            height    = int(row[7])
            numPixels = width*height
            if numPixels > bestNumPixels:
                output.width     = width
                output.height    = height

        # Try to find an associated RAW file
        thisRaw = source_image_utils.getRawPath(output.mission, output.roll, output.frame)
        if os.path.exists(thisRaw):
            output.rawPath = thisRaw
        else:
            print 'Did not find: ' + thisRaw

        # TODO: Handle images with no RAW data
        # Get the image size
        if output.rawPath:
            (output.width, output.height) = \
              source_image_utils.getRawImageSize(output.rawPath)

        if output.width == 0:
            [outputPath, exifSourcePath] = source_image_utils.getSourceImage(output)
            output.width, output.height = IrgGeoFunctions.getImageSize(outputPath)
        print "width is %d" % output.width
        print "height is %d" % output.height
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
Exemple #34
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