def joinBnxFiles(varsP, bnxFiles):
    """After image processing, merge results into all.bnx.
    """
    #the old way was to use this fn which simply copies lines
    # while this is fine most of the time, RefAligner is more sophisticated,
    # so it should be more robust to use RefAligner
    #molecule.joinBnxFiles(bnxFiles, self.bnxFile)            

    #this used to be called writeIntermediate
    varsP.writeListToFile(bnxFiles, varsP.bnxTarget)

    # args, jobName, expectedResultFile, uniqueString
    args = [varsP.RefAlignerBin, "-if", varsP.bnxTarget, "-merge", "-bnx", "-o", varsP.bnxFile.replace(".bnx",""), "-f"]
    if varsP.stdoutlog :
        args.extend( ['-stdout', '-stderr'] )
    #print "joinBnxFiles: args:", args

    jobwrapper = mthread.jobWrapper(varsP, "joinBnxFiles")
    jobwrapper.addJob( mthread.singleJob(args, "joinBnxFiles", varsP.bnxFile, "joinBnxFiles") )
    jobwrapper.multiThreadRunJobs(1)
    jobwrapper.doAllPipeReport()

    success = jobwrapper.allResultsFound()
    if not success :
        varsP.updatePipeReport("ERROR in performImageAnalysis: joinBnxFiles failed. Check: "+varsP.bnxTarget+"\n")

    # this is just putting the path of bnxFile in bnxTarget
    # on second thought, if I don't do this, then SampleCharModule will run on each bnx individually
    #if success :
    #    varsP.writeListToFile([varsP.bnxFile], varsP.bnxTarget)

    #sense of return of allResultsFound is opposite of return of performImageAnalysis:
    # allResultsFound is True for all jobs success, False for any jobs fail
    # performImage analysis return is 1 for failure
    return not success
def performImageAnalysis(varsP, bypass=False, quality=True, forceonecolor=False):
    """Top level function for instrument scaling, image handling, bnx encoding
    
    """
    #print "bypass = "******"ERROR in performImageAnalysis: no images found in paths in "+varsP.imgFile+"\n")
        return 1 #this is an error--new convention is to return 1 on error
    
    processImagesJobSet = mthread.jobWrapper(varsP, groupName = 'Image Transfer and Processing', throttle=8)
    expID = 1
    devices = []
    allJobs = []
    bnxFiles = [] #only used if bypass--see below
    for remoteDataLocation in remoteDataLocations:
        expTag = '%02d' % expID
        expID += 1
        localPath = os.path.join(varsP.localRoot, expTag + '/')
        #if expTag == "04" : #debug
        #print "data\n:", remoteDataLocation, "\n" #debug
        curDevice = Device(varsP, expTag, remoteDataLocation, localPath, bypass, quality, forceonecolor)
        for sJob in curDevice.getTargetJobs():
            if sJob : #empty list is returned if target mol file already exists
                processImagesJobSet.addJob(sJob)
        devices.append(curDevice)
        if bypass :
            bnxFiles.append(curDevice.bnxFile)
     
    if bypass:
        return #bnxFiles #no longer need to return anything--this is not an error

    processImagesJobSet.multiThreadRunJobs(varsP.nThreads, sleepTime =0.25)
    #pipeReport += processImagesJobSet.makeRunReport()
    #pipeReport += processImagesJobSet.makeParseReport()
    #varsP.updatePipeReport(pipeReport, printalso=False)
    processImagesJobSet.doAllPipeReport()
    
    if varsP.lambdaRef:
        mapLambdaJobSet = mthread.jobWrapper(varsP, groupName = 'Map Lambda')
    for device in devices:
        device.findSNRCutoff()
        if varsP.lambdaRef:
            for sJob in device.getLambdaMapJobs():
                if sJob :
                    mapLambdaJobSet.addJob(sJob)
                
    if varsP.lambdaRef:
        mapLambdaJobSet.multiThreadRunJobs(varsP.nThreads, sleepTime =0.25)
        pipeReport =  mapLambdaJobSet.makeRunReport()
        pipeReport += mapLambdaJobSet.makeParseReport()
        for device in devices:
            device.processLambdaMapResults()
        varsP.updatePipeReport(pipeReport, printalso=False)    

    #still need to writeCorrectedBnx
    targetLog = ''
    scanLog = ''
    deviceLog = ''
    bnxFiles = []
    for i,device in enumerate(devices):
        device.writeCorrectedBnx()
        if device.bnxFile : #this is nulled if above failed
            bnxFiles.append(device.bnxFile)
        if i == 0:
            #targetLog += device.getTargetReport(headerOnly=True) + '\n'
            deviceLog += device.getDeviceReport(headerOnly=True) + '\n'
        scanLog   += device.getScanReport() + '\n'
        #targetLog += device.getTargetReport() + '\n'
        deviceLog += device.getDeviceReport() + '\n'

    #remove targetLog here; put it in pipeReport also, 
    # and do it at the beginning, not the end, of processing
    #varsP.updateInfoReport(targetLog + '\n' + scanLog + '\n' + deviceLog + '\n')
    varsP.updateInfoReport(scanLog + '\n' + deviceLog + '\n')

    #return bnxFiles #instead of returning here, merge here, then return
    return joinBnxFiles(varsP, bnxFiles) #see return of this fn