#!/usr/bin/env python
# ctf3dsetup - program to setup command files for reconstruction with 3D CTF correction
#
# Author: David Mastronarde
#
# $Id: ctf3dsetup,v 3c6bbe9deed5 2025/10/24 17:15:55 mast $
#

progname = 'ctf3dsetup'
prefix = 'ERROR: ' + progname + ' - '

def nextComName(sync = True):
   global comNum
   if sync:
      comName = fmtstr('{}-{:03d}-sync.com', outRoot, comNum)
   else:
      comName = fmtstr('{}-{:03d}.com', outRoot, comNum)
   comNum += 1
   return comName

#### MAIN PROGRAM  ####
#
# load System Libraries
import os, sys, glob, math

#
# Setup runtime environment
if os.getenv('IMOD_DIR') != None:
   IMOD_DIR = os.environ['IMOD_DIR']
   if sys.platform == 'cygwin' and sys.version_info[0] > 2:
      IMOD_DIR = IMOD_DIR.replace('\\', '/')
      if IMOD_DIR[1] == ':' and IMOD_DIR[2] == '/':
         IMOD_DIR = '/cygdrive/' + IMOD_DIR[0].lower() + IMOD_DIR[2:]
   sys.path.insert(0, os.path.join(IMOD_DIR, 'pylib'))
   from imodpy import *
   addIMODbinIgnoreSIGHUP()
else:
   sys.stdout.write(prefix + " IMOD_DIR is not defined!\n")
   sys.exit(1)

#
# load IMOD Libraries
from pip import *
from pysed import *
from tomocoords import *

boundPixels = parallelBoundarySize()
outRoot = 'ctf3d'
warnXtiltCrit = 0.3
rawStackName = ''
splitCorrSize = ''
tempStacks = ''

# Fallbacks from ../manpages/autodoc2man 3 1 ctf3dsetup
options = ["reccom:TiltCommandFile:FN:", "ctfcom:CorrectionComFile:FN:",
           "slabs:NumberOfSlabs:I:", "thickness:SlabThicknessInNm:I:",
           "invert:InvertSlabZOffsets:I:", "adjust:AdjustForAlignZShift:B:",
           "parallel:RunSlabsInParallel:B:", "procs:NumberOfProcessors:I:",
           "perproc:ChunksPerProcessor:I:", "erase:EraseFiducials:B:",
           "goldcom:GoldEraserComFile:FN:", "filter:FilterIn2D:B:",
           "2dcom:2DFilterComFile:FN:", "unaligned:UseUnalignedImages:B:",
           "reduce:FourierReduceByFactor:I:", "raw:RawStackFile:FN:",
           "pixel:RawPixelSize:F:", "xform:AlignTransformFile:FN:", "axis:AxisAngle:F:",
           "vertical:VerticalSlices:B:", "oldstyle:OldStyleXtilting:B:",
           "tempdir:TemporaryDirectory:CH:", "leave:LeaveTempFiles:B:",
           "boundary:BoundaryPixels:I:", "help:usage:B:"]

(numOpts, numNonOpts) = PipReadOrParseOptions(sys.argv, options, progname, 1, 1, 0)

# Get the com file name, derive a root name and new com file name, check exists
tiltComFile = PipGetInOutFile('TiltCommandFile', 0)
(tiltComFile, tiltRootname) = completeAndCheckComFile(tiltComFile)

# get or derive ctf correction file and eraser file
ctfComFile = getOrDeriveComFile('CorrectionComFile', 'ctfcorrection', tiltComFile,
                                'CTF correction')

doErase = PipGetBoolean('EraseFiducials', 0)
if doErase:
   eraseComFile =  getOrDeriveComFile('GoldEraserComFile', 'golderaser', tiltComFile,
                                      'erasing gold')

# Determine filtering
doFilter = PipGetBoolean('FilterIn2D', 0)
if doFilter:
   mtfComFile = getOrDeriveComFile('2DFilterComFile', 'mtffilter', tiltComFile, 
                                   '2D filtering')

adjustForZShift = PipGetBoolean('AdjustForAlignZShift', 0)

# See if using raw input
useRawInput = PipGetBoolean('UseUnalignedImages', 0)

if useRawInput or adjustForZShift:

   # Figure out the axis and extension if possible regardless
   (comExt, ifDual, rawRoot, typeExt, rawExt) = findRootAxisAndExtensions()
   axisLet = None
   if ifDual == 0:
      axisLet = ''
   elif ifDual == 2:
      if 'tilta' in tiltComFile:
         axisLet = 'a'
      elif 'tiltb' in tiltComFile:
         axisLet = 'b'
   
if useRawInput:

   # Get raw stack name 
   rawStackName = PipGetString('RawStackFile', '')
   if not rawStackName:
      if rawExt and rawRoot and axisLet != None:
         rawRoot += axisLet
         rawStackName = rawRoot + '.' + rawExt
      else:
         exitError('The raw stack name must be entered, it cannot be deduced')
   if not os.path.exists(rawStackName):
      exitError('The raw stack file does not exist: ' + rawStackName)

   (aliXformFile, rawBinning, rawPixSize) = getEssentialRawOptions(rawRoot)

   try:
      (nxCorr, nyCorr, nzCorr) = getmrcsize(rawStackName)
   except ImodpyError:
      exitFromImodError(progname)

   splitCorrSize = fmtstr('-size {},{},{}', nxCorr // rawBinning, nyCorr // rawBinning,
                           nzCorr)

   # Get the axis angle and whether X/Y are transposed
   (axisAngle, transposeXY) = getAxisAngleAndTranspose(axisLet)

# get other options
numSlabs = PipGetInteger('NumberOfSlabs', 0)
maxSlabThick = PipGetInteger('SlabThicknessInNm', 0)
if numSlabs <= 0 and maxSlabThick <= 0:
   exitError('You must enter either the number of slabs or a slab thickness in nm')
if numSlabs > 0 and maxSlabThick > 0:
   exitError('You cannot enter both the number of slabs and a slab thickness')
if numSlabs > 0 and numSlabs < 3:
   exitError('The number of slabs to compute at different heights must be entered ' +\
             'and must be at least 3')

slabsInParallel = PipGetBoolean('RunSlabsInParallel', 0)
numProcs = PipGetInteger('NumberOfProcessors', 8)
procsEntered = PipGetErrNo() == 0
if slabsInParallel and procsEntered:
   exitError('You cannot enter a number of processors when running slabs in parallel')
chunksPerProc = PipGetInteger('ChunksPerProcessor', 3)
if slabsInParallel and PipGetErrNo() == 0:
   exitError('You cannot enter chunks per processor when running slabs in parallel')

invertZoffsets = PipGetInteger('InvertSlabZOffsets', -1)
boundPixels = PipGetInteger('BoundaryPixels', boundPixels)
leaveTemp = PipGetBoolean('LeaveTempFiles', 0)
tempDir = PipGetString('TemporaryDirectory', '.')
if not os.path.isdir(tempDir) or not os.access(tempDir, os.W_OK):
   exitError('Cannot write to ' + tempDir + ' as a temporary directory')
tempDir = tempDir.replace('\\', '/')
doVert = PipGetBoolean('VerticalSlices', 0)
oldStyle = PipGetBoolean('OldStyleXtilting', 0)
if doVert and oldStyle:
   exitError('You cannot enter options for both vertical slices and old-style X tilting')

if adjustForZShift:
   if axisLet == None:
      exitError('Cannot adjust for Z shift; the axis could not be determined from ' + \
                'files in the directory')
   alignComFile = 'align' + axisLet + '.com'
   alignLines = readTextFile(alignComFile, 'Tiltalign command file')
   alignZshift = optionValue(alignLines, 'AxisZShift', FLOAT_VALUE, numVal = 1)   
   
if tiltRootname == 'tilta' or tiltRootname == 'tiltb':
   outRoot += tiltRootname[4]

tiltLines = readTextFile(tiltComFile, 'Tilt command file')
ctfLines = readTextFile(ctfComFile, 'CTF correction command file')
if doFilter:
   mtfLines = readTextFile(mtfComFile, '2D filtering command file')
if doErase:
   eraseLines = readTextFile(eraseComFile, 'Gold erasing command file')

sirtIter = optionValue(tiltLines, 'sirtiterations', 1, 1, numVal = 1)
if sirtIter:
   exitError('The Tilt command file has a SIRTiterations entry; this cannot be used' +\
             'with 3D CTF correction')
ubThickness = optionValue(tiltLines, 'thickness', 1, 1, numVal = 1)
binning = optionValue(tiltLines, 'imagebinned', 1, 1, numVal = 1)
recfile = optionValue(tiltLines, 'outputfile', 0, 1)
alifile = optionValue(tiltLines, 'inputproj', 0, 1)
tiltMode = optionValue(tiltLines, 'mode', 1, 1, numVal = 1)
shiftArr = optionValue(tiltLines, 'shift', 2, 1)
pixelSize = optionValue(ctfLines, 'PixelSize', FLOAT_VALUE, numVal = 1) 
defocusTol = optionValue(ctfLines, 'DefocusTol', INT_VALUE, numVal = 1) 
expandFac = optionValue(ctfLines, 'ExpandedByFactor', FLOAT_VALUE, numVal = 1)
if pixelSize and expandFac:
   pixelSize /= expandFac

offsetSign = 1.
invertOpt = optionValue(ctfLines, 'InvertTiltAngles', BOOL_VALUE)
if invertOpt and invertZoffsets < 0:
   invertZoffsets = 1
if invertZoffsets > 0:
   offsetSign = -1.

# Get other parameters, make sure aligned stack is not corrected and get its size
(nx, ny, nz, xaxisTilt, useGPU, ctfXtilt, ctfUseGPU, aliPixSize, ctfInput, ctfOutput) = \
   getCTFoptionsCheckIfCorrected(progname, tiltLines, ctfLines, rawStackName, doFilter)

if not recfile:
   exitError('Cannot find output file name in ' + tiltComFile)

if not binning:
   binning = 1
if not ubThickness:
   exitError('Cannot find thickness in ' + tiltComFile)

# Modify for raw input: aliPixSize was the pixel size of the raw stack
if useRawInput:
   checkForDistortion(axisLet, 1, progname)
   if expandFac:
      exitError('You cannot reconstruct from raw images when applying an expansion ' +\
                'factor')
   binning = rawBinning
   if not rawPixSize:
      rawPixSize = getFallbackRawPixel(aliPixSize, axisLet, comExt)
   aliBinning = pixelSize / rawPixSize
   pixelSize = rawPixSize * rawBinning
   aliPixSize *= rawBinning

   # For raw input:
   # binning = rawBinning is the amount it is going to be binned
   # rawPixSize could have been entered as option in nm, otherwise
   #  it is size in nm from header if != 1., otherwise from track.com
   # aliPixSize was originally the raw header pixel size in A and is now the pixel size
   #  in A that will be used for raw input
   # pixelSize is the rawPixelSize in nm times raw binning, so size in nm
   # aliBinning is the pixel size from ctfcorrection divided by rawPixSize

   nxRaw = nxFull = nx
   nyRaw = nyFull = ny
   if transposeXY:
      nxFull = nyRaw
      nyFull = nxRaw
   nx = nxFull // binning
   ny = nyFull // binning

if not pixelSize or not ctfInput or not ctfOutput:
   exitError('Cannot find needed information in ' + ctfComFile + \
             ' (PixelSize, input or output file)')

# Take care of back transforming the eraser model
if useRawInput and doErase:
   rawEraseFid = backTransformEraseModel(eraseLines, aliXformFile, rawPixSize * 10.)
   tempStacks = rawEraseFid

   # The radius to erase must be modified by ratio of stack binning at which it
   # was set/tested and the raw stack binning
   radius = optionValue(eraseLines, 'BetterRadius', FLOAT_VALUE, numVal = 1)
   if radius:
      needRadFix = True

      # But try to fix it from info in the golderaser log
      (eraseRoot, ext) = os.path.splitext(eraseComFile)
      eraseLog = eraseRoot + '.log'
      if os.path.exists(eraseLog):
         logLines = readTextFile(eraseLog, "Log file from gold erasing");
         for line in logLines:
            if '[CCE1]' in line:
               lsplit = line.split()
               try:
                  testRadius = float(lsplit[1])
                  testPixel = float(lsplit[2])
               except (IndexError, ValueError):
                     exitError('[CCE1] line in gold erasing log file does not ' + \
                               'have the correct form')

               if not testPixel:
                  exitError('Cannot scale the bead erasing diameter ' +\
                            'because of bad output in the golderaser log file; ' +\
                            'rerun ' + eraseComFile)
               needRadFix = False
               wasRad = radius * aliBinning / rawBinning
               radius = testRadius * testPixel / aliPixSize
               
      if needRadFix:
         if not os.path.exists(alifile):
            binOrUnbin = 'unbinned pixels'
            if aliBinning > 1.01:
               binOrUnbin = fmtstr('pixels binned by {:.0f}', aliBinning)
            prnstr(fmtstr('WARNING: Ctf3dsetup - Assuming that the entered diameter ' +\
                          'of {:.1f} for bead erasing applies to {}', radius * 2.,
                          binOrUnbin))
         radius *= aliBinning / rawBinning


startingZshift = 0
xShift = 0.
if shiftArr:
   xShift = shiftArr[0]
if shiftArr and len(shiftArr) > 1:
   startingZshift = shiftArr[1]

zShiftAdjustment = 0.
if adjustForZShift:
   zShiftAdjustment = (alignZshift + startingZshift) / binning

# Figure out slab sizes and number of slabs
pixelsThickness = ubThickness // binning
nmThickness = int(round(pixelsThickness * pixelSize))
if maxSlabThick > 0:
   numSlabs = (nmThickness + maxSlabThick - 1) // maxSlabThick
   if numSlabs < 3:
      exitError(fmtstr('The entered slab thickness would give only {} slabs; you ' +\
                       'must enter a value less than {}', numSlabs, nmThickness // 2))
pixelsPerSlab = pixelsThickness // numSlabs
nmPerSlab = pixelsPerSlab * pixelSize
prnstr(fmtstr('{} slabs of thickness {:.0f} nm will be computed', numSlabs, nmPerSlab))
slabNmInt = int(round(nmPerSlab))
if defocusTol:
   defocusTol = min(defocusTol, slabNmInt)
else:
   defocusTol = slabNmInt

# set up the slab limits
remainder = pixelsThickness % numSlabs
shiftsPixels = []
offsetsPix = []
thicknesses = []
slabStart = 0
for ind in range(numSlabs):
   slabEnd = slabStart + pixelsPerSlab
   if ind < remainder:
      slabEnd += 1

   # Positive tilt shift for slabs at the bottom
   # But also positive Z offset for lower slabs, because negative Y is really positive Z
   # If adjusting for Z shift, the slab shift that is in the opposite direction from
   # that Z shift is is the slab that started out at 0, so ADD the Z shift
   slabShift = (pixelsThickness - (slabEnd + slabStart)) / 2.
   shiftsPixels.append(slabShift * binning + startingZshift)
   offsetsPix.append((slabShift + zShiftAdjustment) * offsetSign)
   thicknesses.append((slabEnd - slabStart) * binning)
   slabStart = slabEnd

# Test for X tilt consistency and if it matters
if not useRawInput:
   checkXtiltCtfVsRec(xaxisTilt, ctfXtilt, warnXtiltCrit, ny * pixelSize, nmPerSlab,
                      'slab', progname)

# figure out maximum views entry to splitcorrection
if useGPU == None:
   useGPU = -1
if not slabsInParallel:
   if useGPU >= 0 and not procsEntered:
      numProcs = 1
   if numProcs > 1:
      numChunks = numProcs * chunksPerProc
      maxSlices = max(1, (nz + numChunks - 1) // numChunks)

cleanChunkFiles(outRoot)
boundList = glob.glob(outRoot + '-bound-*.info')
if boundList:
   cleanupFiles(boundList)

(setName, aliExt) = os.path.splitext(ctfInput)
if setName.endswith('_ali') and aliExt != '.ali':
   setName = setName[:-4]
(aliRoot, aliExt) = os.path.splitext(ctfOutput)
(recRoot, recExt) = os.path.splitext(recfile)
if recExt != '.rec':
   recExt = '_rec' + recExt
aliRoot = tempDir + '/' + aliRoot

# If using raw input, set up input name and optional binning com file
comNum = 1
if useRawInput:
   ctfInput = rawStackName
   if binning > 1:
      ctfInput = fmtstr('{}_red{}_tmp{}', aliRoot, binning, aliExt)
      tempStacks += ' "' + ctfInput + '"'
      newstLines = [fmtstr('$newstack -ftreduce {} {} "{}"', binning, rawStackName, 
                           ctfInput)]
      writeTextFile(nextComName(), newstLines)

# If filtering, do it in an initial command file
if doFilter:
   filtInput = ctfInput
   ctfInput = fmtstr('{}_filt_tmp{}', aliRoot, aliExt)
   tempStacks += ' "' + ctfInput + '"'
   mtfsed = [sedModify('InputFile', filtInput, delim = '|'),
             sedModify('OutputFile', ctfInput, delim = '|'),
             sedModify('PixelSize', pixelSize, delim = '|')]
   mtfMod = pysed(mtfsed, mtfLines, delim = '|')
   writeTextFile(nextComName(), mtfMod)
   if not useRawInput:
      splitCorrSize = fmtstr('-size {},{},{}', nx, ny, nz)

# Work on slabs now
recNameLines = []
for slab in range(numSlabs):
   if slabsInParallel:
      aliName = fmtstr('{}_{:02d}{}', aliRoot, slab, aliExt)
      eraseName = fmtstr('{}_erase_{:02d}{}', aliRoot, slab, aliExt)
   else:
      aliName = fmtstr('{}_tmp{}', aliRoot, aliExt)
      eraseName = fmtstr('{}_erase_tmp{}', aliRoot, aliExt)
      
   recName = fmtstr('{}_{:02d}{}', aliRoot, slab, recExt)
   recNameLines.append('InputFile ' + recName)
   tiltsed = [sedModify('InputProjections', aliName, delim = '|'),
              sedModify('OutputFile', recName, delim = '|'),
              sedModify('THICKNESS', thicknesses[slab], delim = '|')] + \
      sedDelAndAdd('SHIFT', fmtstr('{} {}', round(float(xShift), 2), 
                                   round(float(shiftsPixels[slab]), 2)), 'THICKNESS',
                   delim = '|')
   if useRawInput:
      tiltsed += sedDelAndAdd('UseUnalignedImages', 1, 'THICKNESS', delim = '|') + \
      sedDelAndAdd('AlignTransformFile', aliXformFile, 'THICKNESS', delim = '|') + \
      [sedModify('FULLIMAGE', fmtstr('{} {}', nxFull, nyFull), delim = '|'),
      sedModify('SUBSETSTART', '0 0', delim = '|'),
      sedModify('IMAGEBINNED', str(binning), delim = '|')]

   tiltMod = pysed(tiltsed, tiltLines, None, True, delim = '|')

   ctfsed = [sedModify('OutputFileName', aliName, delim = '|')] + \
      sedDelAndAdd('OffsetInZ', offsetsPix[slab], 'DefocusFile', delim = '|') + \
      sedDelAndAdd('UseGPU', useGPU, 'DefocusFile', delim = '|') + \
      sedDelAndAdd('DefocusTol', fmtstr('{}', defocusTol), 'DefocusFile', delim = '|')
   if tempStacks or useRawInput:
      ctfsed += [sedModify('InputStack', ctfInput, delim = '|')]
   if useRawInput:

      ctfsed += ['|TransformFile|d',
                 sedModify('PixelSize', pixelSize, delim = '|')] + \
         sedDelAndAdd('XAxisTilt', xaxisTilt, 'DefocusFile', delim = '|') + \
         sedDelAndAdd('AxisAngle', axisAngle, 'DefocusFile', delim = '|')
                
   ctfMod = pysed(ctfsed, ctfLines, delim = '|')
   
   if doErase:
      eraseSed = [sedModify('InputFile', aliName, delim = '|'),
                  sedModify('OutputFile', eraseName, delim = '|')]
      if useRawInput:
         eraseSed += [sedModify('ModelFile', rawEraseFid, delim = '|')]

         # The radius to erase must be modified by ratio of stack binning at which it
         # was set/tested and the raw stack binning
         radius = optionValue(eraseLines, 'BetterRadius', FLOAT_VALUE, numVal = 1)
         if radius:
            needRadFix = True
            (eraseRoot, ext) = os.path.splitext(eraseComFile)
            eraseLog = eraseRoot + '.log'
            if os.path.exists(eraseLog):
               logLines = readTextFile(eraseLog, "Log file from gold erasing");
               for line in logLines:
                  if '[CCE1]' in line:
                     lsplit = line.split()
                     try:
                        testRadius = float(lsplit[1])
                        testPixel = float(lsplit[2])
                     except (IndexError, ValueError):
                           exitError('[CCE1] line in gold erasing log file does not ' + \
                                     'have the correct form')

                     needRadFix = False
                     wasRad = radius * aliBinning / rawBinning
                     radius = testRadius * testPixel / aliPixSize
                     
            if needRadFix:   
               radius *= aliBinning / rawBinning
            eraseSed += [sedModify('BetterRadius', radius, delim = '|')]
      
      eraseMod = pysed(eraseSed, eraseLines, delim = '|')
      eraseMod.append(fmtstr('$b3drename "{}" "{}"', eraseName, aliName))
   
   # For parallel slabs, combine the two command into one file and output it
   if slabsInParallel:
      if doErase:
         allLines = ctfMod + eraseMod + tiltMod
      else:
         allLines = ctfMod + tiltMod
      allLines.append('$b3dremove "' + aliName + '"')
      writeTextFile(nextComName(False), allLines)

   else:

      # For sequential slabs, write each com either to numbered sync (1 proc) or to
      # temp files
      if numProcs > 1:
         ctfComOut = tempDir + '/ctfcorrection.tmp.com'
         tiltComOut = tempDir + '/tilt.tmp.com'
      else:
         ctfComOut = nextComName()
         if doErase:
            eraseComOut = nextComName()
         tiltComOut = nextComName()
         if not leaveTemp:
            tiltMod.append('$b3dremove ' + aliName)
      writeTextFile(ctfComOut, ctfMod)
      writeTextFile(tiltComOut, tiltMod)
      if numProcs == 1 and doErase:
         writeTextFile(eraseComOut, eraseMod)

      # Now if multiple processors/GPUs, split each
      if numProcs > 1:
         try:
            splitLines = runcmd(fmtstr('splitcorrection -i {} -o -m {} -b {} -uni ' +\
                                       '{} -r {} "{}"', comNum, maxSlices, boundPixels, 
                                       splitCorrSize, outRoot, ctfComOut))
            numAdded = findSplitComNumber(splitLines, 'output of splitcorrection')
            comNum += numAdded

            if doErase:
               writeTextFile(nextComName(), eraseMod)

            comLines = ['CommandFile  ' + tiltComOut,
                        'RootNameOfOutput  ' + outRoot,
                        'ProcessorNumber  ' + str(numProcs),
                        'TargetChunks  ' + str(numProcs * chunksPerProc),
                        'BoundaryPixels  ' + str(boundPixels),
                        'InitialComNumber  ' + str(comNum),
                        'OpenForMoreComs  1',
                        'UniqueInfoFile  1',
                        fmtstr('DimensionsOfStack  {},{}', nx, ny)]
            if doVert:
               comLines.append('VerticalSlices  1')
            if oldStyle:
               comLines.append('OldStyleXtiltPenalty  0.5')

            splitLines = runcmd('splittilt -StandardInput', comLines)
            numAdded = findSplitComNumber(splitLines, 'output of splittilt')
            comNum += numAdded
            if not leaveTemp:
               lastSync = fmtstr('{}-{:03d}-sync.com', outRoot, comNum - 1)
               lastLines = readTextFile(lastSync)
               lastLines.append('$b3dremove "' + aliName + '"')
               writeTextFile(lastSync, lastLines)

         except ImodpyError:
            exitFromImodError(progname)
         
# Time to assemble the slabs and clean up
if not slabsInParallel and numProcs > 1:
   cleanupFiles([ctfComOut, tiltComOut])

assembleLines = [];
if tiltMode and tiltMode == 12:
   assembleLines = ['$setenv IMOD_WRITE_FLOATS_16BIT 1']
assembleLines += ['$assemblevol -StandardInput',
                  'NumberOfFilesInY ' + str(numSlabs),
                  'OutputFile ' + setName + '_3dctf' + recExt] + recNameLines
if not leaveTemp:
   assembleLines.append(fmtstr('$b3dremove -g "{}_[0-9][0-9]{}"', aliRoot, recExt))
   if slabsInParallel:
      assembleLines.append(fmtstr('$b3dremove -g "{}_[0-9][0-9]{}"', aliRoot, aliExt))
   if tempStacks:
         assembleLines.append('$b3dremove ' + tempStacks)

writeFinishAndMessage(assembleLines, outRoot, comNum, not slabsInParallel and 
                      numProcs == 1)
   
sys.exit(0)
