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 #2
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 #3
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 #5
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
Exemple #6
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
def writeLabelFile(imagePath, outputPath, dataSetName, versionId, description, extraData=None):
    """Write out a .LBL file formatted for the PDS"""
    
    # Call functions to automatically obtain some data from the referenced image
    imageSize   = IrgGeoFunctions.getImageSize(imagePath)
    boundingBox = IrgGeoFunctions.getImageBoundingBox(imagePath)    

    # Obtain the ASP version string
    aspVersionString = IrgAspFunctions.getAspVersionStrings()
    
    imageGeoInfo = IrgGeoFunctions.getImageGeoInfo(imagePath)

    projCenterLatitude  = imageGeoInfo['standard_parallel_1']
    projCenterLongitude = imageGeoInfo['central_meridian']
        
    # Currently assuming pixels are the same size
    metersPerPixel = abs(imageGeoInfo['pixel size'][0])
    
    # Compute pixels per degree
    lonSpanDegrees     = boundingBox[1] - boundingBox[0]
    latSpanDegrees     = boundingBox[3] - boundingBox[2]
    pixelsPerDegreeLon = imageSize[0] / lonSpanDegrees
    pixelsPerDegreeLat = imageSize[1] / latSpanDegrees
    pixelsPerDegree    = (pixelsPerDegreeLat + pixelsPerDegreeLon) / 2.0

    # Computed by dividing 'Origin' by 'Pixel Size'
    lineProjOffset   = imageGeoInfo['origin'][0] / imageGeoInfo['pixel size'][1]
    sampleProjOffset = imageGeoInfo['origin'][1] / imageGeoInfo['pixel size'][0]
        
    
    labelFile = open(outputPath, 'w')
    
    labelFile.write('PDS_VERSION_ID            = PDS3\n')
    
    labelFile.write('/* The source image data definition. */\n')
    labelFile.write('^IMAGE        = ' + os.path.basename(outputPath) +'\n')
    
    labelFile.write('/* Identification Information  */\n')
    labelFile.write('DATA_SET_ID               = ""\n') # Someone will tell us what to put here
    labelFile.write('DATA_SET_NAME             = ""\n') # Someone will tell us what to put here
    labelFile.write("VOLUME_ID                 = ''\n") # Someone will tell us what to put here
    labelFile.write('PRODUCER_INSTITUTION_NAME = "NASA AMES RESEARCH CENTER"\n')
    labelFile.write('PRODUCER_ID               = NASA IRG\n')
    labelFile.write('PRODUCER_FULL_NAME        = "ZACHARY MORATTO"\n')
    labelFile.write("PRODUCT_ID                = " + dataSetName + "\n")
    labelFile.write("PRODUCT_VERSION_ID        = " + versionId + "\n")
    labelFile.write('PRODUCT_TYPE              = "RDR"\n')
    labelFile.write('INSTRUMENT_HOST_NAME      = "LUNAR RECONNAISSANCE ORBITER"\n')
    labelFile.write('INSTRUMENT_HOST_ID        = "LRO"\n')
    labelFile.write('INSTRUMENT_NAME           = "LUNAR RECONNAISSANCE ORBITER CAMERA"\n')
    labelFile.write('INSTRUMENT_ID             = "LROC"\n')
    labelFile.write('TARGET_NAME               = MOON\n')
    labelFile.write('MISSION_PHASE_NAME        = "NOMINAL MISSION"\n')
    labelFile.write("""RATIONALE_DESC            = "Created at the request of NASA's Exploration\n""") 
    labelFile.write('                            Systems Mission Directorate to support future\n')
    labelFile.write('                            human exploration"\n')
    labelFile.write('SOFTWARE_NAME             = "'+ aspVersionString[0] +' | '+ aspVersionString[2] +'"\n')
    labelFile.write('DESCRIPTION               = "' + description + '"\n')
    labelFile.write('\n')
    labelFile.write('/* Time Parameters */\n')
    labelFile.write('PRODUCT_CREATION_TIME        = ' + time.strftime("%Y-%m-%dT%H:%M:%S") + '\n')
    labelFile.write('\n')
    labelFile.write('/* NOTE:                                                                   */\n')
    labelFile.write('/* This raster image is composed of a set of pixels that represent finite  */\n')
    labelFile.write('/* areas, and not discrete points.  The center of the upper left pixel is  */\n')
    labelFile.write('/* defined as line and sample (1.0,1.0). The                               */\n')
    labelFile.write('/* [LINE,SAMPLE]_PROJECTION_OFFSET elements are the pixel offset from line */\n')
    labelFile.write('/* and sample (1.0,1.0) to the map projection origin (defined by the       */\n')
    labelFile.write('/* CENTER_LATITUDE and CENTER_LONGITUDE elements).  These offset values    */\n')
    labelFile.write('/* are positive when the map projection origin is to the right or below    */\n')
    labelFile.write('/* the center of the upper left pixel.                                     */\n')
    if extraData: # Location for additional notes
        labelFile.write(extraData)
    labelFile.write('\n')
    labelFile.write('OBJECT = IMAGE_MAP_PROJECTION\n')
    labelFile.write('    MAP_PROJECTION_TYPE          = EQUIRECTANGULAR\n') # Specified by +proj=eqc
    labelFile.write('    PROJECTION_LATITUDE_TYPE     = PLANETOCENTRIC\n')  #From gdalinfo?
    labelFile.write('    A_AXIS_RADIUS                = 1737.4 <KM>\n') # Fixed lunar radius
    labelFile.write('    B_AXIS_RADIUS                = 1737.4 <KM>\n')
    labelFile.write('    C_AXIS_RADIUS                = 1737.4 <KM>\n')
    labelFile.write('    COORDINATE_SYSTEM_NAME       = PLANETOCENTRIC\n') #From gdalinfo?
    labelFile.write('    POSITIVE_LONGITUDE_DIRECTION = EAST\n') #From gdalinfo?
    labelFile.write('    KEYWORD_LATITUDE_TYPE        = PLANETOCENTRIC\n') #From gdalinfo?
    labelFile.write('    /* NOTE:  CENTER_LATITUDE and CENTER_LONGITUDE describe the location   */\n')
    labelFile.write('    /* of the center of projection, which is not necessarily equal to the  */\n')
    labelFile.write('    /* location of the center point of the image.                          */\n')
    labelFile.write('    CENTER_LATITUDE              = ' + str(projCenterLatitude)  + ' <DEG>\n')
    labelFile.write('    CENTER_LONGITUDE             = ' + str(projCenterLongitude) + ' <DEG>\n')
    labelFile.write('    LINE_FIRST_PIXEL             = 1\n')
    labelFile.write('    LINE_LAST_PIXEL              = ' + str(imageSize[1] + 1) + '\n')
    labelFile.write('    SAMPLE_FIRST_PIXEL           = 1\n')
    labelFile.write('    SAMPLE_LAST_PIXEL            = ' + str(imageSize[0] + 1) + '\n')
    labelFile.write('    MAP_PROJECTION_ROTATION      = 0.0 <DEG>\n') #From gdalinfo (probably always zero)
    labelFile.write('    MAP_RESOLUTION               = ' + str(round(pixelsPerDegree,2)) +' <PIX/DEG>\n')
    labelFile.write('    MAP_SCALE                    = ' + str(round(metersPerPixel,4)) + ' <METERS/PIXEL>\n')
    labelFile.write('    MAXIMUM_LATITUDE             = ' + str(boundingBox[3]) + ' <DEG>\n') 
    labelFile.write('    MINIMUM_LATITUDE             = ' + str(boundingBox[2]) + ' <DEG>\n')
    labelFile.write('    EASTERNMOST_LONGITUDE        = ' + str(boundingBox[0]) + ' <DEG>\n')
    labelFile.write('    WESTERNMOST_LONGITUDE        = ' + str(boundingBox[1]) + ' <DEG>\n')
    labelFile.write('    LINE_PROJECTION_OFFSET       = ' + str(round(lineProjOffset,2))  +' <PIXEL>\n')
    labelFile.write('    SAMPLE_PROJECTION_OFFSET     = ' + str(round(sampleProjOffset,2)) +' <PIXEL>\n')
    labelFile.write('END_OBJECT = IMAGE_MAP_PROJECTION\n')
    labelFile.write('\n')
    labelFile.write('END\n')
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
    labelFile.close()
    return True
def 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)
Exemple #10
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 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 __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 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)
Exemple #14
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)
Exemple #15
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