def main(argsIn):

    try:
        usage = '''usage: process_icebridge_run.py <image_folder> <camera_folder>
                      <lidar_folder> <output_folder>'''
                      
        parser = optparse.OptionParser(usage=usage)

        # Data selection optios
        parser.add_option('--start-frame', dest='startFrame', default=-1,
                          type='int', help='The frame number to start processing with.')
        parser.add_option('--stop-frame', dest='stopFrame', default=-1,
                          type='int', help='The frame number to finish processing with.')        
        parser.add_option('--south', action='store_true', default=False, dest='isSouth',  
                          help='MUST be set if the images are in the southern hemisphere.')

        # Processing options
        parser.add_option('--stereo-arguments', dest='stereoArgs', default='',
                          help='Additional argument string to be passed to the stereo command.')

        parser.add_option('--bundle-length', dest='bundleLength', default=2,
                          type='int', help='Number of images to bundle adjust and process at once.')
        parser.add_option('--image-stereo-interval', dest='imageStereoInterval', default=None,
                          type='int', help='Advance this many frames to get the stereo pair.  Default is auto-calculate')

        parser.add_option('--solve-intrinsics', action='store_true', default=False,
                          dest='solve_intr',  
                          help='If to float the intrinsics params.')

        #parser.add_option('--dem-resolution', dest='demResolution', default=0.4,
        #                  type='float', help='Generate output DEMs at this resolution.')

        parser.add_option('--max-displacement', dest='maxDisplacement', default=20,
                          type='float', help='Max displacement value passed to pc_align.')


        # Performance options
        parser.add_option('--num-processes', dest='numProcesses', default=1,
                          type='int', help='The number of simultaneous processes to run.')
        parser.add_option('--num-threads', dest='numThreads', default=None,
                          type='int', help='The number threads to use per process.')

        # Action options
        parser.add_option('--interactive', action='store_true', default=False, dest='interactive',  
                          help='If to wait on user input to terminate the jobs.')
        parser.add_option('--log-batches', action='store_true', default=False, dest='logBatches',  
                          help="Just log the batch commands to a file.")
        parser.add_option('--cleanup', action='store_true', default=False, dest='cleanup',  
                          help='If the final result is produced delete intermediate files.')
        parser.add_option('--many-ip', action='store_true', default=False, dest='manyip',  
                          help='If to use a lot of IP in bundle adjustment from the beginning.')
        parser.add_option('--dry-run', action='store_true', default=False, dest='dryRun',  
                          help="Print but don't launch the processing jobs.")

        parser.add_option('--ortho-folder', dest='orthoFolder', default=None,
                          help='Use ortho files to adjust processing to the image spacing.')
        parser.add_option('--fireball-folder', dest='fireballFolder', default=None,
                          help='Location of fireball DEMs for comparison.')

        parser.add_option('--reference-dem', dest='referenceDem', default=None,
                          help='Reference DEM used to calculate the expected GSD.')

        (options, args) = parser.parse_args(argsIn)

        if len(args) < 4:
            print(usage)
            return 0

        imageFolder  = args[0]
        cameraFolder = args[1]
        lidarFolder  = args[2]
        outputFolder = args[3]

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

    os.system("ulimit -c 0") # disable core dumps
    os.system("umask 022")   # enforce files be readable by others
    
    # Check the inputs
    for f in [imageFolder, cameraFolder, lidarFolder]:
        if not os.path.exists(f):
            logger.error('Input folder '+ f +' does not exist!')
            return 0

    asp_system_utils.mkdir_p(outputFolder)

    suppressOutput = False
    redo           = False

    logger.info('\nStarting processing...')
    
    # Get a list of all the input files
    imageCameraPairs = icebridge_common.getImageCameraPairs(imageFolder, cameraFolder, 
                                                            options.startFrame, options.stopFrame,
                                                            logger)
    numFiles = len(list(imageCameraPairs))
    if numFiles < 2:
        raise Exception('Failed to find any image camera pairs!')
    
    # Check that the files are properly aligned
    lastFrame = -1
    availableFrames = []
    for (image, camera) in imageCameraPairs:
        frameNumber = icebridge_common.getFrameNumberFromFilename(image)
        availableFrames.append(frameNumber)
        if (icebridge_common.getFrameNumberFromFilename(camera) != frameNumber):
            logger.error('Error: input files do not align!\n' + str((image, camera)))
            return -1
        if frameNumber <= lastFrame:
            logger.error('Error: input frames not sorted properly!\n')
            return -1
        lastFrame = frameNumber

    # Do not compute output resolution. Will be overwritten anyway per frame. 
    ## Set the output resolution as the computed mean GSD
    ## TODO: This should be cashed, and recomputed only when the batch file changes!
    #NUM_GSD_FRAMES = 20
    #logger.info('Computing GSD with ' + str(NUM_GSD_FRAMES) + ' frames.')
    #gsdFrameSkip = len(imageCameraPairs) / NUM_GSD_FRAMES
    #if gsdFrameSkip < 1:
    #    gsdFrameSkip = 1
    #medianGsd = getRunMedianGsd(imageCameraPairs, options.referenceDem, options.isSouth,
    #                            gsdFrameSkip)
    #outputResolution = icebridge_common.gsdToDemRes(medianGsd)
    #logger.info('OUTPUT_RESOLUTION: ' + str(outputResolution))
    outputResolution = 0.4 

    # Generate a map of initial camera positions
    orbitvizBefore = os.path.join(outputFolder, 'cameras_in.kml')
    vizString  = ''
    for (image, camera) in imageCameraPairs: 
        vizString += camera+' '
    cmd = 'orbitviz_pinhole --hide-labels -o '+ orbitvizBefore +' '+ vizString
    logger.info('Running orbitviz on input files...')

    # Suppress (potentially long) output
    asp_system_utils.executeCommand(cmd, orbitvizBefore, True, redo)

    # Set up options for process_icebridge_batch
    extraOptions = ''
    if options.numThreads:
        extraOptions += ' --num-threads ' + str(options.numThreads)
    if options.solve_intr:
        extraOptions += ' --solve-intrinsics '
    if options.isSouth:
        extraOptions += ' --south '
    if options.maxDisplacement:
        extraOptions += ' --max-displacement ' + str(options.maxDisplacement)
    if options.fireballFolder:
        extraOptions += ' --fireball-folder ' + str(options.fireballFolder)
    if options.cleanup:
        extraOptions += ' --cleanup '
    if options.manyip:
        extraOptions += ' --many-ip '

    # We ran this before, as part of fetching, so hopefully all the data is cached
    forceAllFramesInRange = False
    (breaks, largeSkips) = getImageSpacing(options.orthoFolder, availableFrames,
                                           options.startFrame, options.stopFrame,
                                           forceAllFramesInRange)
    if options.imageStereoInterval: 
        logger.info('Using manually specified image stereo interval: ' +
                    str(options.imageStereoInterval))
        largeSkips = [] # Always use a manually specified skip interval
    else:
        options.imageStereoInterval = 1

    sleepTime = 20

    # If all we are doing is logging commands then one process is sufficient.
    # - Wipe the output file while we are at it.
    batchLogPath = ''
    batchNum = 0

    if options.logBatches:
        options.numProcesses = 1
        sleepTime    = 1
        batchLogPath = os.path.join(outputFolder, BATCH_COMMAND_LOG_FILE)
        os.system('rm -f ' + batchLogPath)
        logger.info('Just generating batch log file '+batchLogPath+', no processing will occur.')

    logger.info('Starting processing pool with ' + str(options.numProcesses) +' processes.')
    pool = NonDaemonPool(options.numProcesses)
    
    # Call process_icebridge_batch on each batch of images.
    # - Batch size should be the largest number of images which can be effectively bundle-adjusted.
    taskHandles           = []
    batchImageCameraPairs = []
    frameNumbers          = []
    i = 0 # The frame index that starts the current batch
    while True: # Loop for adding batches

        # Bugfix: arrived at the last feasible frame (maybe there are more but
        # they lack cameras).
        if i >= len(list(imageCameraPairs)):
            break
        
        firstBundleFrame = icebridge_common.getFrameNumberFromFilename(imageCameraPairs[i][0])
        
        # Determine the frame skip amount for this batch (set by the first frame)
        thisSkipInterval = options.imageStereoInterval
        if firstBundleFrame in largeSkips:
            #print(" ", firstBundleFrame, " in largeskips!" )
            thisSkipInterval = largeSkips[firstBundleFrame]
        thisBatchSize = options.bundleLength + thisSkipInterval - 1

        # Keep adding frames until we have enough or run out of frames
        j = i # The frame index that ends the batch
        while True:
            
            if j >= len(imageCameraPairs):
                # Bugfix: arrived at the last feasible frame.
                break
            
            frameNumber = icebridge_common.getFrameNumberFromFilename(imageCameraPairs[j][0])
            # Useful debugging code
            #print("Framenumber is ", frameNumber)
            #if int(frameNumber) > 8531:
            #    #    pass
            #    for t in range(8340, 8360):
            #        print("i frame is ", t, imageCameraPairs[t][0])
            #    print("breaks are ", breaks)
            #    sys.exit(1)
                
            # Update conditions
            hitBreakFrame = (frameNumber in breaks)
            
            lastFrame     = (frameNumber > options.stopFrame) or (j >= numFiles)
            endBatch      = ( len(frameNumbers) >= thisBatchSize )

            if lastFrame or endBatch:
                break # The new frame is too much, don't add it to the batch
        
            # Add frame to the list for the current batch
            batchImageCameraPairs.append(imageCameraPairs[j])
            frameNumbers.append(frameNumber)
            
            if hitBreakFrame:
                logger.info("Hit a break, won't start a batch with frame: " + str(frameNumber))
                break # Break after this frame, it is the last one added to the batch.
            
            j = j + 1
        # Done adding frames to this batch

        if len(frameNumbers) <= thisSkipInterval:
            logger.info('Batch from frame: ' + str(firstBundleFrame) +
                        ' is too small to run. Skipping.')
        else:

            # Submit the batch
            if not options.logBatches:
                logger.info('Processing frame number: ' + str(firstBundleFrame))
                     
            # The output folder is named after the first and last frame in the batch.
            # We count on this convention in blend_dems.py.
            batchFolderName  = icebridge_common.batchFolderName(frameNumbers[0], frameNumbers[-1],
                                                                options.bundleLength)
            thisOutputFolder = os.path.join(outputFolder, batchFolderName)

            if not options.logBatches:
                logger.info('Running processing batch in output folder: ' +  \
                            thisOutputFolder + '\n' + 'with options: '    +  \
                            extraOptions + ' --stereo-arguments '         +  \
                            options.stereoArgs)

            if not options.dryRun:
                # Generate the command call
                taskHandles.append(pool.apply_async(processBatch, 
                    (batchImageCameraPairs, lidarFolder, options.referenceDem, thisSkipInterval,
                     thisOutputFolder, extraOptions, 
                     outputResolution, options.stereoArgs, batchNum, batchLogPath)))
            batchNum += 1
            
        
        # Reset these lists
        batchImageCameraPairs = []
        frameNumbers          = []
        
        # Advance to the frame that starts the next batch
        if hitBreakFrame:
            # When we hit a break in the frames we need to start the
            # next batch after the break frame
            #print("Hit break frame for i, j, frameNumber", i, j, frameNumber)
            i = j + 1
        else:
            # Start in the next frame that was not used as a "left" stereo image.
            i = i + options.bundleLength - 1
        
        if lastFrame:
            break # Quit the main loop if we hit the end of the frame list.

    # End of loop through input file pairs
    logger.info('Finished adding ' + str(len(taskHandles)) + ' tasks to the pool.')
    
    # Wait for all the tasks to complete
    icebridge_common.waitForTaskCompletionOrKeypress(taskHandles, logger, options.interactive, 
                                                     quitKey='q', sleepTime=sleepTime)

    # Either all the tasks are finished or the user requested a cancel.
    # Clean up the processing pool
    icebridge_common.stopTaskPool(pool)

    logger.info('Finished process_icebridge_run.') # to avoid ending a log with 'waiting ...'
def solveIntrinsics_Part2(options, imageFolder, cameraFolder, lidarFolder,
                          orthoFolder, processedFolder, isSouth, logger):
    '''Create a camera model with optimized intrinsics. By now we
    processed a bunch of images and created bundle-adjusted and
    pc_aligned cameras and DEMs while using a camera model with
    distortion implemented using RPC coefficients which was obtained
    from the photometrics model. We now use the obtained cameras as
    inputs to a bundle adjust problem where we will optimize the
    intrinsics, including the distortion RPC coefficients, using the
    lidar as an external constraint, and many dense IP pairs and
    triplets (no quadruplets yet, even if 4 images overlap).'''

    # Get a list of all the input files
    imageCameraPairs = icebridge_common.getImageCameraPairs(
        imageFolder, cameraFolder, options.startFrame, options.stopFrame,
        logger)

    # The paired lidar file for the first image should be huge enough to contain
    # all images.
    lidarFile = icebridge_common.findMatchingLidarFile(imageCameraPairs[0][0],
                                                       lidarFolder)
    logger.info('Found matching lidar file ' + lidarFile)
    lidarCsvFormatString = icebridge_common.getLidarCsvFormat(lidarFile)

    numFiles = len(imageCameraPairs)
    if numFiles < 2:
        raise Exception('Failed to find any image camera pairs!')
    if numFiles % 2 != 0:
        raise Exception(
            "When solving for intrinsics, must have an even number of frames to use."
        )

    # Collect pc_align-ed cameras, unaligned disparities, and dense match files
    images = []
    cameras = []
    dispFiles = []
    for it in range(numFiles / 2):
        begFrame = options.startFrame + 2 * it
        endFrame = begFrame + 1
        batchFolderName = icebridge_common.batchFolderName(
            begFrame, endFrame, options.bundleLength)
        thisOutputFolder = os.path.join(processedFolder, batchFolderName)

        # Find all the cameras after bundle adjustment and pc_align.
        pattern = icebridge_common.getAlignedBundlePrefix(
            thisOutputFolder) + '*.tsai'
        alignedCameras = glob.glob(pattern)
        if len(alignedCameras) != options.bundleLength:
            raise Exception("Expected " + str(options.bundleLength) +
                            " cameras, here's what " +
                            " was obtained instead: " +
                            " ".join(alignedCameras))
        img0 = ""
        cam0 = ""
        img1 = ""
        cam1 = ""
        for cam in alignedCameras:
            frame = icebridge_common.getFrameNumberFromFilename(cam)
            if begFrame == frame:
                img0 = imageCameraPairs[2 * it][0]
                cam0 = cam
            if endFrame == frame:
                img1 = imageCameraPairs[2 * it + 1][0]
                cam1 = cam
        images.append(img0)
        images.append(img1)
        cameras.append(cam0)
        cameras.append(cam1)

        # Unaligned disparity
        stereoFolder = os.path.join(thisOutputFolder, 'stereo_pair_' + str(0))
        currDispFiles = glob.glob(
            os.path.join(stereoFolder, '*unaligned-D.tif'))
        if len(currDispFiles) != 1:
            raise Exception("Expecting a single unaligned disparity file in " +
                            stereoFolder)
        dispFiles.append(currDispFiles[0])

    # Match files
    matchFiles = []
    for it in range(numFiles - 1):
        begFrame = options.startFrame + it
        endFrame = begFrame + 1
        batchFolderName = icebridge_common.batchFolderName(
            begFrame, endFrame, options.bundleLength)
        thisOutputFolder = os.path.join(processedFolder, batchFolderName)
        stereoFolder = os.path.join(thisOutputFolder, 'stereo_pair_' + str(0))
        DISP_PREFIX = "disp-"
        currMatchFiles = glob.glob(
            os.path.join(stereoFolder, '*' + DISP_PREFIX + '*.match'))
        if len(currMatchFiles) != 1:
            raise Exception("Expecting a single dense match file in " +
                            stereoFolder)
        matchFiles.append(currMatchFiles[0])

    # Create output directory for bundle adjustment and copy there the match files
    baDir = os.path.join(processedFolder, "bundle_intrinsics")
    baPrefix = os.path.join(baDir, "out")
    os.system("mkdir -p " + baDir)
    for matchFile in matchFiles:
        dstFile = os.path.basename(matchFile)
        dstFile = dstFile.replace(DISP_PREFIX, '')
        dstFile = os.path.join(baDir, dstFile)
        cmd = "cp -f " + matchFile + " " + dstFile
        logger.info(cmd)
        os.system(cmd)

    # The bundle adjustment
    cmd = "bundle_adjust " + " ".join(images) + " " +  " ".join(cameras) + \
            ' --reference-terrain ' + lidarFile + \
            ' --disparity-list "' + " ".join(dispFiles) + '"' + \
            ' --datum wgs84 -t nadirpinhole --local-pinhole --robust-threshold 2' + \
            ' --camera-weight 1 --solve-intrinsics --csv-format ' + lidarCsvFormatString + \
            ' --overlap-limit 1 --max-disp-error 10 --max-iterations 100 ' + \
            ' --parameter-tolerance 1e-12 -o ' + baPrefix
    logger.info(cmd)
    os.system(cmd)

    # Generate DEMs of residuals before and after optimization
    projString = icebridge_common.getEpsgCode(isSouth, asString=True)
    for val in ['initial', 'final']:
        cmd = 'point2dem --t_srs ' + projString + ' --tr 2'    + \
              ' --csv-format 1:lon,2:lat,4:height_above_datum' + \
              ' ' + baPrefix + '-' + val + '_residuals_no_loss_function_pointmap_point_log.csv'
        logger.info(cmd)
        os.system(cmd)
        cmd = 'point2dem --t_srs ' + projString + ' --tr 2'    + \
              ' --csv-format 1:lon,2:lat,4:height_above_datum' + \
              ' ' + baPrefix + '-' + val +'_residuals_no_loss_function_reference_terrain.txt'
        logger.info(cmd)
        os.system(cmd)

    # Look at the latest written tsai file, that will be the optimized distortion file.
    # Force the initial rotation and translation to be the identity, this is
    # expected by ortho2pinhole.
    outFiles = filter(os.path.isfile, glob.glob(baPrefix + '*.tsai'))
    outFiles.sort(key=lambda x: os.path.getmtime(x))
    optFile = outFiles[-1]
    logger.info("Reading optimized file: " + optFile)
    with open(optFile, 'r') as f:
        lines = f.readlines()
    for it in range(len(lines)):
        lines[it] = lines[it].strip()
        if re.match("^C\s*=\s*", lines[it]):
            lines[it] = "C = 0 0 0"
        if re.match("^R\s*=\s*", lines[it]):
            lines[it] = "R = 1 0 0 0 1 0 0 0 1"

    # Write the final desired optimized RPC file
    logger.info("Writing final optimized file: " + options.outputCalCamera)
    os.system("mkdir -p " + os.path.dirname(options.outputCalCamera))
    with open(options.outputCalCamera, 'w') as f:
        for line in lines:
            f.write(line + "\n")
def solveIntrinsics_Part2(options, imageFolder, cameraFolder, lidarFolder, orthoFolder,
                         processedFolder, isSouth, logger):
    
    '''Create a camera model with optimized intrinsics. By now we
    processed a bunch of images and created bundle-adjusted and
    pc_aligned cameras and DEMs while using a camera model with
    distortion implemented using RPC coefficients which was obtained
    from the photometrics model. We now use the obtained cameras as
    inputs to a bundle adjust problem where we will optimize the
    intrinsics, including the distortion RPC coefficients, using the
    lidar as an external constraint, and many dense IP pairs and
    triplets (no quadruplets yet, even if 4 images overlap).'''

    # Get a list of all the input files
    imageCameraPairs = icebridge_common.getImageCameraPairs(imageFolder, cameraFolder, 
                                                            options.startFrame, options.stopFrame,
                                                            logger)
    
    # The paired lidar file for the first image should be huge enough to contain
    # all images.
    lidarFile = icebridge_common.findMatchingLidarFile(imageCameraPairs[0][0], lidarFolder)
    logger.info('Found matching lidar file ' + lidarFile)
    lidarCsvFormatString = icebridge_common.getLidarCsvFormat(lidarFile)

    numFiles = len(imageCameraPairs)
    if numFiles < 2:
        raise Exception('Failed to find any image camera pairs!')
    if numFiles % 2 != 0:
        raise Exception("When solving for intrinsics, must have an even number of frames to use.")


    # Collect pc_align-ed cameras, unaligned disparities, and dense match files
    images = []
    cameras = []
    dispFiles = []
    for it in range(numFiles/2):
        begFrame = options.startFrame + 2*it
        endFrame = begFrame + 1
        batchFolderName  = icebridge_common.batchFolderName(begFrame, endFrame, options.bundleLength)
        thisOutputFolder = os.path.join(processedFolder, batchFolderName)
        
        # Find all the cameras after bundle adjustment and pc_align.
        pattern = icebridge_common.getAlignedBundlePrefix(thisOutputFolder) + '*.tsai'
        alignedCameras = glob.glob(pattern)
        if len(alignedCameras) != options.bundleLength:
            raise Exception("Expected " + str(options.bundleLength) + " cameras, here's what " +
                            " was obtained instead: " + " ".join(alignedCameras))
        img0 = ""; cam0 = ""; img1 = ""; cam1 = ""
        for cam in alignedCameras:
            frame = icebridge_common.getFrameNumberFromFilename(cam)
            if begFrame == frame:
                img0 = imageCameraPairs[2*it][0]
                cam0 = cam
            if endFrame == frame:
                img1 = imageCameraPairs[2*it+1][0]
                cam1 = cam
        images.append(img0);  images.append(img1)
        cameras.append(cam0); cameras.append(cam1)

        # Unaligned disparity
        stereoFolder = os.path.join(thisOutputFolder, 'stereo_pair_'+str(0))
        currDispFiles = glob.glob(os.path.join(stereoFolder, '*unaligned-D.tif'))
        if len(currDispFiles) != 1:
            raise Exception("Expecting a single unaligned disparity file in " + stereoFolder)
        dispFiles.append(currDispFiles[0])
        
    # Match files
    matchFiles = []
    for it in range(numFiles-1):
        begFrame = options.startFrame + it
        endFrame = begFrame + 1
        batchFolderName  = icebridge_common.batchFolderName(begFrame, endFrame, options.bundleLength)
        thisOutputFolder = os.path.join(processedFolder, batchFolderName)
        stereoFolder = os.path.join(thisOutputFolder, 'stereo_pair_'+str(0))
        DISP_PREFIX = "disp-"
        currMatchFiles = glob.glob(os.path.join(stereoFolder, '*' + DISP_PREFIX + '*.match'))
        if len(currMatchFiles) != 1:
            raise Exception("Expecting a single dense match file in " + stereoFolder)
        matchFiles.append(currMatchFiles[0])

    # Create output directory for bundle adjustment and copy there the match files
    baDir = os.path.join(processedFolder, "bundle_intrinsics")
    baPrefix = os.path.join(baDir, "out")
    os.system("mkdir -p " + baDir)
    for matchFile in matchFiles:
        dstFile = os.path.basename(matchFile)
        dstFile = dstFile.replace(DISP_PREFIX, '')
        dstFile = os.path.join(baDir, dstFile)
        cmd = "cp -f " + matchFile + " " + dstFile
        logger.info(cmd)
        os.system(cmd)

    # The bundle adjustment
    cmd = "bundle_adjust " + " ".join(images) + " " +  " ".join(cameras) + \
            ' --reference-terrain ' + lidarFile + \
            ' --disparity-list "' + " ".join(dispFiles) + '"' + \
            ' --datum wgs84 -t nadirpinhole --create-pinhole-cameras --robust-threshold 2' + \
            ' --camera-weight 1 --solve-intrinsics --csv-format ' + lidarCsvFormatString + \
            ' --overlap-limit 1 --max-disp-error 10 --max-iterations 100 ' + \
            ' --parameter-tolerance 1e-12 -o ' + baPrefix
    logger.info(cmd)
    os.system(cmd)

    # Generate DEMs of residuals before and after optimization
    projString = icebridge_common.getEpsgCode(isSouth, asString=True)
    for val in ['initial', 'final']:
        cmd = 'point2dem --t_srs ' + projString + ' --tr 2'    + \
              ' --csv-format 1:lon,2:lat,4:height_above_datum' + \
              ' ' + baPrefix + '-' + val + '_residuals_no_loss_function_pointmap_point_log.csv'
        logger.info(cmd)
        os.system(cmd)
        cmd = 'point2dem --t_srs ' + projString + ' --tr 2'    + \
              ' --csv-format 1:lon,2:lat,4:height_above_datum' + \
              ' ' + baPrefix + '-' + val +'_residuals_no_loss_function_reference_terrain.txt'
        logger.info(cmd)
        os.system(cmd)

    # Look at the latest written tsai file, that will be the optimized distortion file.
    # Force the initial rotation and translation to be the identity, this is
    # expected by ortho2pinhole.
    outFiles = filter(os.path.isfile, glob.glob(baPrefix + '*.tsai'))
    outFiles.sort(key=lambda x: os.path.getmtime(x))
    optFile = outFiles[-1]
    logger.info("Reading optimized file: " + optFile)
    with open(optFile, 'r') as f:
        lines = f.readlines()
    for it in range(len(lines)):
        lines[it] = lines[it].strip()
        if re.match("^C\s*=\s*", lines[it]):
            lines[it] = "C = 0 0 0"
        if re.match("^R\s*=\s*", lines[it]):
            lines[it] = "R = 1 0 0 0 1 0 0 0 1"

    # Write the final desired optimized RPC file
    logger.info("Writing final optimized file: " + options.outputCalCamera)
    # Below is a bugfix, must take full path to find the dir, otherwise it may fail.
    os.system("mkdir -p " + os.path.dirname(os.path.abspath(options.outputCalCamera)))
    with open(options.outputCalCamera, 'w') as f:
        for line in lines:
            f.write(line + "\n")
    # Check the inputs
    for f in [imageFolder, cameraFolder, lidarFolder]:
        if not os.path.exists(f):
            logger.error('Input folder ' + f + ' does not exist!')
            return 0

    asp_system_utils.mkdir_p(outputFolder)

    suppressOutput = False
    redo = False

    logger.info('\nStarting processing...')

    # Get a list of all the input files
    imageCameraPairs = icebridge_common.getImageCameraPairs(
        imageFolder, cameraFolder, options.startFrame, options.stopFrame,
        logger)
    numFiles = len(imageCameraPairs)
    if numFiles < 2:
        raise Exception('Failed to find any image camera pairs!')

    # Check that the files are properly aligned
    lastFrame = -1
    availableFrames = []
    for (image, camera) in imageCameraPairs:
        frameNumber = icebridge_common.getFrameNumberFromFilename(image)
        availableFrames.append(frameNumber)
        if (icebridge_common.getFrameNumberFromFilename(camera) !=
                frameNumber):
            logger.error('Error: input files do not align!\n' +
                         str((image, camera)))