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
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
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
def _estimateRegistration(self, baseImage, otherImage, outputPath): '''Writes an estimated registration transform to a file based on geo metadata.''' # This function assumes the images are in the same projection system! # Get the projection bounds and size in both images baseGeoInfo = IrgGeoFunctions.getImageGeoInfo(baseImage, False) otherGeoInfo = IrgGeoFunctions.getImageGeoInfo(otherImage, False) baseProjBounds = baseGeoInfo['projection_bounds'] otherProjBounds = otherGeoInfo['projection_bounds'] baseImageSize = baseGeoInfo['image_size'] otherImageSize = otherGeoInfo['image_size'] # Now estimate the bounding box of the other image in the base image topLeftCoord = projCoordToPixelCoord(otherProjBounds[0], otherProjBounds[3], baseGeoInfo) transform = MosaicUtilities.SpatialTransform() transform.setShift(topLeftCoord[0], topLeftCoord[1]) transform.write(outputPath) return topLeftCoord
def 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)
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)
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
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 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
def getBoundingBox(fileList): """Return the bounding box for this data set in the format (minLon, maxLon, minLat, maxLat)""" return IrgGeoFunctions.getImageBoundingBox(fileList[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)
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)
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
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