#!/usr/bin/env python
# tomosnapshot - program to collect small files from etomo processing
#
# Author: David Mastronarde
#
# $Id: tomosnapshot,v ac5286652d37 2025/01/29 20:50:08 mast $
#

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

# The lists of different kinds of files for each kind of etomo data set

edfSetFiles = ['.rawtlt', '_peak.mod', '.prexf', '.prexg', '.seed', '.fid', '.3dmod',
               '.tlt', 'fid.xyz', '.resid', '.resmod', '.tltxf', '_fid.xf', 'local.xf',
               '.matmod', '.pl', '.ecd', '_orig.seed', '.xtilt', '.maggrad', '.zfac',
               '.defocus', '_erase.fid', '_3dfind.mod', '_flat.mod', '_flat.mod',
               '_fid.tlt', '.erase', '_rawbound.mod', '_afsbound.mod', '_ptbound.mod',
               '_pt.fid', '.xf']
edfAxisHeaders = ['_orig.st', '.st', '.preali', '.ali', '.rec', '.mat', '_full.rec']
edfSingleFiles = ['solvezero.xf', 'solve.xf', 'refine.xf', 'warp.xf', 'inverse.xf', 
                  'patch.out', 'patch_vector.mod', 'patch_vector_ccc.mod',
                  'patch_region.mod', 'processchunks.out', 'savework',
                  'processchunksa.out', 'processchunksb.out', 'processchunks.csh',
                  'processchunksa.csh', 'processchunksb.csh', 'transferfid.coord', 
                  'combine.out', 'volcombine.csh', 'tomopitcha.mod', 'tomopitchb.mod',
                  'tomopitch.mod', 'rotation.xf']
edfSingleHeaders = []
edfAxisCopyHeads = ['.st']
edfAxisThumbs = ['.st', '.rec', '.mat', '_full.rec']
edfSingleThumbs = ['mid.rec', 'mida.rec', 'midb.rec', 'bot.rec', 'bota.rec', 'botb.rec',
                   'top.rec', 'topa.rec', 'topb.rec', 'sum.rec']
ejfSetFiles = ['.info', '.tomoxf', '.tomoxg', '.xf', '_auto.xcxf', '_auto.xf',
               '_empty.xf', '_midas.xf', '.sqzxf', '.xpndxf', '_refine.mod', '_join.mod',
               '_refine.alimod', '_refine.xf', '_refine.xg', '_refinejoin.xg']
ejfAxisHeaders = []
ejfSingleFiles = []
ejfSingleHeaders = []
epeSetFiles = ['.prm']
epeAxisHeaders = []
epeSingleFiles = ['processchunks.out', 'processchunks.csh']
epeSingleHeaders = []
nadSetFiles = []
nadAxisHeaders = []
nadSingleFiles = []
nadSingleHeaders = []
eppSetFiles = []
eppAxisHeaders = []
eppSingleFiles = ['processchunks.out', 'processchunks.csh']
eppSingleHeaders = []
essSetFiles = ['.xf', '.xg', '_auto.xcxf', '_auto.xf', '_auto.linxf', '_midas.xf', 
               '_bound.mod']
essAxisHeaders = ['_preblend.mrc', '_ali.mrc']
essSingleFiles = []
essSingleHeaders = []
essAxisThumbs = ['_preblend.mrc', '_ali.mrc']
ebtSetFiles = ['_project.log', '.adoc']

allSetFiles = [edfSetFiles, ejfSetFiles, epeSetFiles, nadSetFiles, eppSetFiles, 
               essSetFiles, ebtSetFiles]
allAxisHeaders = [edfAxisHeaders, ejfAxisHeaders, epeAxisHeaders, nadAxisHeaders,
                  eppAxisHeaders, essAxisHeaders, []]
allSingleFiles = [edfSingleFiles, ejfSingleFiles, epeSingleFiles, nadSingleFiles,
                  eppSingleFiles, essSingleFiles, []]
allSingleHeaders = [edfSingleHeaders, ejfSingleHeaders, epeSingleHeaders,
                    nadSingleHeaders, eppSingleHeaders, essSingleHeaders, []]
allAxisThumbs = [edfAxisThumbs, [], [], [], [], essAxisThumbs, []]
allSingleThumbs = [edfSingleThumbs, [], [], [], [], [], []]
allAxisCopyHeads = [edfAxisCopyHeads, [], [], [], [], [], []]

# The extensions of file types
extList = ['edf', 'ejf', 'epe', 'epp', 'ess', 'ebt']

# Keys for different types of files in each file type
allSetKeys = [['Setup.DatasetName'], ['Join.RootName'], ['Peet.RootName'],
              ['AnisotropicDiffusion.RootName', 'Parallel.RootName'],
              ['SerialSections.RootName'], ['meta.RootName']]

# Some global variables
tempdir = '.'
copylogs = []
tarlist = []
thumbnails = []
headerCopies = []

# Constants for the types
TOMO = 0
JOIN = 1
PEET = 2
NAD = 3
PARALLEL = 4
SERIAL = 5
BATCH = 6

# FUNCTIONS

# To get a value from the etomo file or a prm file
def getKeyValue(etomoLines, key):
   for i in range(len(etomoLines)):
      line = etomoLines[i]
      if line.startswith(key):
         if line.find('{') >= 0 and line.find('}') < 0:
            for j in range(i + 1, len(etomoLines)):
               line += ' ' + etomoLines[j]
               if etomoLines[j].find('}') >= 0:
                  break
         lsplit = line.split('=')
         if len(lsplit) > 1:
            return lsplit[1].strip().strip('{}').strip()
   return None


# Parse an entry in a prm file and if it contains strings, return list of unique ones
def prmUniqueEntries(prmLines, key):
   value = getKeyValue(prmLines, key)
   if value == None or value.find("'") < 0:
      return None
   entries = []
   vsplit = value.split(',')
   for entry in vsplit:
      entry = entry.strip().strip("'")
      if entry not in entries:
         entries.append(entry)
   return entries


# Copy an error log and change permissions; or copy other files to be deleted later
def copyErrLog(errfile):
   global copylogs, tarlist
   errfile = errfile.replace('\\', '/')
   base = os.path.basename(errfile)
   if not os.path.exists(errfile) or base in copylogs:
      return
   try:

      # You need to use copy2, not copy then copystat, to get dates right
      shutil.copy2(errfile, tempdir)
      copylogs.append(base)

      # Add to tar list if it is other directory, or if it is not a log file; otherwise
      # the globs of logs will take care of it
      if tempdir != '.' or not base.endswith('.log'):
         tarlist.append(base)
      copiedFile = os.path.join(tempdir, base)
      try:
         os.chmod(copiedFile, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
      except:
         warning('Failed to change mode of ' + copiedFile)
      
   except:
      warning('Failed to copy ' + errfile)


# Make a thumbnail of the image file
def makeThumbnail(imfile):
   global thumbnails
   if not doThumbnails or not os.path.exists(imfile):
      return
   shrink = 4.
   maxSize = 512. * 512.
   try:
      (nx, ny, nz) = getmrcsize(imfile)
      if nx * ny > maxSize * (shrink ** 2):
         shrink = math.sqrt(nx * ny / maxSize)
      base = os.path.basename(imfile + '.tn')
      outfile = os.path.join(tempdir, base)
      newstcom = ['InputFile ' + imfile,
                  'OutputFile ' + outfile,
                  'ModeToOutput 0',
                  'FloatDensities 1',
                  'SectionsToRead ' + str(nz // 2),
                  'ShrinkByFactor ' + str(shrink)]
      runcmd('newstack -StandardInput', newstcom)
      thumbnails.append(outfile)
      tarlist.append(base)
   except ImodpyError:
      warning('Error making thumbnail of ' + imfile)

def copyHeader(imfile):
   global headerCopies
   if not os.path.exists(imfile):
      return
   try:
      base = os.path.basename(imfile + '.head')
      outfile = os.path.join(tempdir, base)
      runcmd('copyheader "' + imfile + '" "' + outfile + '"')
      headerCopies.append(outfile)
      tarlist.append(base)
   except ImodpyError:
      warning('Error copying header of ' + imfile)


# Expand a file list by adding possible stack extensions instead of .st, and standard
# type extension reformulations
def expandFileList(fileList):
   toAdd = []
   for ending in fileList:
      if ending.endswith('.st'):
         for ext in stackExts[1:]:
            toAdd.append(ending.replace('.st', '.' + ext))
      else:
         for ext in stdTypeExts[1:]:
            toAdd.append(datasetFilename(ending, typext = ext))
   fileList += toAdd


# Print a warning
def warning(message):
   prnstr('WARNING: ' + progname + ' - ' + message)


# Strip line endings from program output and write file
def stripAndWrite(outfile, lines):
   for i in range(len(lines)):
      lines[i] = lines[i].rstrip('\r\n')
   writeTextFile(outfile, lines)


# Remove a file and warn if fails
def removeWarn(rfile):
   try:
      os.remove(rfile)
   except:
      warning('Failed to remove ' + rfile)


#### MAIN PROGRAM  ####
#
# load System Libraries
import os, sys, glob, shutil, tempfile, tarfile, stat, 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 *

# Startup
unameFile = 'uname.out'
lslrtFile = 'lslrt.out'

options = ["e:EtomoFile:FN:Etomo data file to define type of snapshot (can be " + \
           "non-option argument)",
           "o:OutputFile:FN:Name of tarred output file (default rootname-snapshot)",
           "w:WriteableDirectory:FN:Location at which to place output and temporary "+ \
           "files",
           "t:Thumbnails:B:Make thumbnails of middle sections of some image files",
           "s:SkipCygcheck:B:Skip running cygcheck on Windows",
           "help:usage:B:Print usage output"]
PipExitOnError(0, prefix)
(numOpts, numNonOpts) = PipParseInput(sys.argv, options)

# Print help
if PipGetBoolean('help', 0):
   prnstr("""tomosnapshot collects small files and other information about
   one form of Etomo processing.
   If no etomo data file is specified, it searches for files in this order:
   tomogram generation (.edj), joining (.ejf), PEET (.epe), NAD (.epe),
   generic parallel processing (.epe), serial sections (.ess), batch (.ebt)
   It must be run from the data directory, but you do not need to have write
   permission in that directory if you specify an alternate writeable directory.""")
   
   PipPrintHelp(progname, 0, 0, 0)
   sys.exit(0)

# Process options
etomoFile = PipGetInOutFile('EtomoFile', 0)
inputDir = ''
if etomoFile:
   etomoFile = etomoFile.replace('\\', '/')
   inputDir = os.path.dirname(etomoFile)
   if not inputDir:
      inputDir = '.'
   inputDir += os.sep

doThumbnails = PipGetBoolean('Thumbnails', 0)
writeableDir = PipGetString('WriteableDirectory', '.')
skipCygcheck = PipGetBoolean('SkipCygcheck', 0)

if not os.path.isdir(writeableDir):
   exitError(writeableDir + ' is not a directory')
if not os.access(writeableDir, os.W_OK):
   exitError('You do not have permission to write in the directory ' + writeableDir)

for ext in extList:
   if etomoFile == None:
      files = glob.glob('*.' + ext)
      if len(files):
         etomoFile = files[0]

# If there is an etomo file at all, search it for the setname of appropriate type
typeInd = 0
setname = None
axisType = None
viewType = None
copylist = []
tarlist = [unameFile, lslrtFile]
if etomoFile:
   copylist.append(etomoFile)
   etomoLines = readTextFile(etomoFile)
   for extInd in range(len(extList)):
      if etomoFile.endswith(extList[extInd]):

         # If the extension matches, check for the keys that go with that extension
         for key in allSetKeys[extInd]:
            setname = getKeyValue(etomoLines, key)
            if setname:
               break
            typeInd += 1

         # If no key is found, it is an error; except fall back to xcorr for an edf
         else:
            if typeInd > 1:
               exitError('Data set root name not found in etomo data file')
            warning('Data set root name not found in etomo data file')
            etomoFile = None
            break

      # done looking if got setname or had to fall back; otherwise increment type
      if setname or etomoFile == None:
         break
      typeInd += len(allSetKeys[extInd])

   else:
      exitError('Specified etomo file ' + etomoFile + ' does not have an appropriate' +\
                ' extension')

   # For edf, look up the axis type; fall back to looking at xcorr.com
   if etomoFile and typeInd == TOMO:
      axisType = getKeyValue(etomoLines, 'Setup.AxisType')
      if not axisType:
         warning('Cannot find axis type in edf file')
         etomoFile = None
      viewType = getKeyValue(etomoLines, 'Setup.ViewType')

# If there was no etomo file found or specified or failure occurred, now do the fallback
if etomoFile == None:
   typeInd = TOMO
   (comExt, dualNum, root, typeExt, stackExt) = findRootAxisAndExtensions()
   if dualNum == 2:
      axisType = 'Dual Axis'
   elif dualNum >= 0:
      axisType = 'Single Axis'
   else:
      if setname:
         exitError('Cannot determine' +\
                   ' axis type from com/pcm files in the input directory')
      else:
         exitError('Cannot find an etomo file or sufficient com/pcm files in the ' +\
                   'input directory to proceed')

   # Get the setname from the analysis if it is still needed
   if not setname:
      if not root:
         exitError('Cannot find dataset name from analysis of files; if running from ' +\
                   'Etomo, do File - Save and try again')
      setname = root

# Now we know the data type, get file lists
oneAxisSetFiles = allSetFiles[typeInd]
oneAxisHeaders = allAxisHeaders[typeInd]
singleFiles = allSingleFiles[typeInd]
singleHeaders = allSingleHeaders[typeInd]
oneAxisCopyHeads = allAxisCopyHeads[typeInd]
oneAxisThumbs = allAxisThumbs[typeInd]
singleThumbs = allSingleThumbs[typeInd]

# Expand some of the file lists for possible stack extensions and naming styles
setRootAndExtension('', '')
stdTypeExts = standardTypeExtensions()
stackExts = allowedRawStackExtensions()
for fileList in (oneAxisHeaders, singleHeaders, oneAxisCopyHeads, oneAxisThumbs,
                 singleThumbs):
   expandFileList(fileList)

naxis = 1
axislet = ''
if axisType == 'Dual Axis':
   naxis = 2
   axislet = 'a'

outputFile = PipGetString('OutputFile', setname + '-snapshot')

# Get a temporary dir if needed, and the com file dir
if writeableDir != '.' or inputDir:
   try:
      tempdir = tempfile.mkdtemp(prefix = 'tomosnaptmp-', dir = writeableDir)
   except Exception:
      exitError("Creating temporary directory to copy files into")
try:
   comdir =  tempfile.mkdtemp(prefix = 'tomosnapshot.cms.', dir = tempdir)
   comdir = comdir.replace('\\', '/')
except Exception:
   exitError("Creating temporary directory for com files")

# Get the directory listing and the uname output
# Find out if there is no cygwin: i.e., it is windows python and cygcheck or uname fails
# 9/16/21: Reinhard Rechel had a machine where uname ran, so do cygcheck first
unameOut = []
winOnly = False
try:
   if 'win32' in sys.platform:
      junkOut = runcmd('cygcheck -h', None, None, 'stdout')

   unameOut = runcmd('uname -a', None, None, 'stdout')
except Exception:
   winOnly = sys.platform.find('win32') >= 0
   
if winOnly:
   winver = sys.getwindowsversion()
   unameOut = [fmtstr('Windows version: {} - {} - {} - {} - {}', winver[0], winver[1],
                      winver[2], winver[3], winver[4])]

# For cygwin, make sure files written by Win programs are readable
elif 'cygwin' in sys.platform and writeableDir == '.':
   try:
      runcmd('chmod u+r *')
   except Exception:
      warning('Command to make all files readable failed')

# Get the Python version
unameOut.append("Python version: " + sys.version)
infolines = runcmd('imodinfo')
if len(infolines):
   unameOut.append(infolines[0])

# Get the names of CUDA libs so we know what package this is
cudaPath = os.path.join(IMOD_DIR, 'qtlib')
if not os.path.exists(cudaPath):
   cudaPath = os.path.join(IMOD_DIR, 'bin')
cudaList = glob.glob(os.path.join(cudaPath, '*cuda*'))
if len(cudaList):
   unameOut += cudaList

for k, v in os.environ.items():
   unameOut.append(k + '=' + v)

# Should we do the registry with -r?  It takes 3 seconds
if (sys.platform.find('cygwin') >= 0 or sys.platform.find('win32') >= 0) and not winOnly \
   and not skipCygcheck:
   try:
      unameOut += runcmd('cygcheck -s -v -r', None, None, 'stdout')
   except ImodpyError:
      warning('Failed to run cygcheck even though uname ran, indicating Cygwin was ' +\
              'present')

# Type-specific additions to lists here:
#
# TOMOGRAM
if typeInd == TOMO:
   pwinfo = glob.glob('*bound.info')
   singleFiles += pwinfo
   afinfo = glob.glob('autofidseed*.info')
   singleFiles += afinfo
   adocs = glob.glob('batch*.adoc') + glob.glob('*emplate*.adoc')
   singleFiles += adocs
   
#
# JOIN
if typeInd == JOIN:
   infofile = setname + '.info'
   if os.path.exists(infofile):
      infolines = readTextFile(infofile)
      nlines = len(infolines)
      if nlines > 3:
         lsplit = infolines[0].split()
         numfiles = 0
         if len(lsplit) > 1:
            numfiles = int(lsplit[0])
         if numfiles > 0:
            for i in range(max(3, nlines - numfiles), nlines):
               singleHeaders.append(infolines[i])
               singleThumbs.append(infolines[i])

   added = ['.sampavg', '.sample', '.join', '_modeled.join', '_trial.join']
   expandFileList(added)
   oneAxisHeaders += added

#
# PEET
if typeInd == PEET:
   motls = glob.glob(setname + '*.csv')
   singleFiles += motls
   refs = glob.glob('*' + setname + '*_Ref*.mrc')
   singleHeaders += refs
   singleThumbs += refs
   avgs = glob.glob('*' + setname + '*_AvgVol*.mrc')
   singleHeaders += avgs
   singleThumbs += avgs
   prmFile = setname + '.prm'
   if os.path.exists(prmFile):
      prmLines = readTextFile(prmFile)
      volumes = prmUniqueEntries(prmLines, 'fnVolume')
      if volumes:
         singleHeaders += volumes
         singleThumbs += volumes
      ref = getKeyValue(prmLines, 'reference')
      if ref.find("'") >= 0:
         singleHeaders.append(ref.strip("'"))
      for key in ('fnModParticle', 'initMOTL'):
         entries = prmUniqueEntries(prmLines, key)
         if entries:
            for entry in entries:

               # If a file with the base name is here, use it by adding to list;
               # if it is not, copy it here and have it deleted at end
               entry = entry.replace('\\', '/')
               base = os.path.basename(entry)
               if os.path.exists(base):
                  singleFiles.append(base)
               else:
                  copyErrLog(entry)

#
# NAD
naddir = None
if typeInd == NAD:
   naddir = 'naddir.' + setname
   added = [setname, os.path.join(naddir, 'test.input')]
   expandFileList(added)
   singleHeaders += added
   singleFiles.append(naddir + '-pc.csh')
   singleFiles.append(os.path.join(naddir, 'processchunks.out'))
   tests = glob.glob(os.path.join(naddir, 'test.K*[0-9]*[^~]'))
   singleHeaders += tests
   
#
# Serial sections
if typeInd == SERIAL:
   if etomoFile:
      stack = getKeyValue(etomoLines, 'SerialSections.Stack')
      if stack:
         singleHeaders.append(stack)
         singleThumbs.append(stack)

#
# Batch tomograms
if typeInd == BATCH:

   # Add the stacks if they are in the table
   keyNum = 1
   while True:
       stack = getKeyValue(etomoLines, 'meta.ref.ebt' + str(keyNum))
       if not stack:
          break
       if getKeyValue(etomoLines, 'meta.row.ebt' + str(keyNum) + '.RowNumber'):
          singleHeaders.append(stack)

          # Strip a or b from dataset name if it is dual and compose adoc name
          dual = getKeyValue(etomoLines, 'meta.row.ebt' + str(keyNum) + '.dual') == 'true'
          (stackRoot, ext) = os.path.splitext(os.path.basename(stack))
          if dual and (stackRoot.endswith('a') or stackRoot.endswith('b')):
             stackRoot = stackRoot[:-1]
          setAdoc = os.path.join(os.path.dirname(stack), setname) + '_' + stackRoot + \
              '.adoc'

          # Add to copy list if it exists
          if os.path.exists(setAdoc):
             copylist.append(setAdoc)
          else:
             
             # If not, find original stack location and check there
             origStack = getKeyValue(etomoLines, 'meta.row.ebt' + str(keyNum) + \
                                        '.OrigStack')
             if origStack:
                setAdoc = os.path.join(os.path.dirname(origStack), setname) + '_' + \
                    stackRoot + '.adoc'
                if os.path.exists(setAdoc):
                   copylist.append(setAdoc)

       keyNum += 1

         
# Run the file listing now that we know if we need a subdir
curDir = os.getcwd()
if naddir:
   os.chdir(naddir)
elif inputDir:
   os.chdir(inputDir)
if winOnly:
   lslrt = runcmd('dir /OD')

else:
   lslrt = runcmd('ls -lrt')
   lslrt += runcmd('ls -ld .')
if naddir or inputDir:
   os.chdir(curDir)
   
stripAndWrite(os.path.join(tempdir, lslrtFile), lslrt)


# Get error logs from elsewhere
if os.path.exists('etomo_err.log'):
   errlines = readTextFile('etomo_err.log')
   for line in errlines:
      if line.startswith('Error log'):
         nameind = line.find('is in ') + 6
         if nameind > 10:
            copyErrLog(line[nameind:])

# Get last 10 from the log directory (may be redundant)
# NOTE csh tomosnapshot has a bug, should be ls -rt
logDir = os.getenv('ETOMO_LOG_DIR')
if logDir == None and os.getenv('HOME'):
   logDir = os.path.join(os.getenv('HOME'), '.etomologs')
if logDir and os.path.exists(logDir) and os.path.isdir(logDir) and \
       os.access(logDir, os.R_OK):
   errlogs = glob.glob(os.path.join(logDir, 'etomo_err*.log'))
   numlogs = len(errlogs)
   if numlogs:

      # sort the logs by newest first (higher mtime)
      sortInd = list(range(numlogs))
      mtimes = []
      for i in sortInd:
         mtimes.append(os.path.getmtime(errlogs[i]))
      if numlogs > 1:
         for i in range(numlogs - 1):
            for j in range(i + 1, numlogs):
               if mtimes[sortInd[i]] < mtimes[sortInd[j]]:
                  temp = sortInd[i]
                  sortInd[i] = sortInd[j]
                  sortInd[j] = temp
      for i in range (min(10, numlogs)):
         copyErrLog(errlogs[sortInd[i]])
               
# Get all com and log files and backups except the ta*.log
# Look in naddir for NAD, or confine to setname-* for parallel processing
globpref = inputDir
if naddir:
   globpref += naddir + os.sep
elif typeInd == PARALLEL:
   globpref += setname + '-'

if typeInd == BATCH:
   globpref = inputDir + setname
else:
   globpref += '*'
comlist = glob.glob(globpref + '.com') + glob.glob(globpref + '.com~')
comlistb = glob.glob(globpref + '.pcm') + glob.glob(globpref + '.pcm~')
loglist = glob.glob(globpref + '.log')
loglistb = glob.glob(globpref + '.log~')
for logs in (loglist, loglistb):
   if len(logs):
      for i in range(len(logs) - 1, -1, -1):
         if os.path.basename(logs[i]).startswith('ta'):
            logs.pop(i)

# Copy the com files to the com dir with .com stripped
for comfile in comlist:
   (base, ext) = os.path.splitext(os.path.basename(comfile.replace('\\', '/')))
   if ext.endswith('~'):
      base += '~'
   try:
      shutil.copy2(comfile, os.path.join(comdir, base))
   except:
      warning("Failed to copy " + comfile + " to " + comdir)

tarlist.append(os.path.basename(comdir))

# Put the logs and backup coms on a copy list
copylist += comlistb + loglist + loglistb

# Loop on axis files, adding files to the copy list if they exist, getting headers
for axnum in range(naxis):
   for ext in oneAxisSetFiles:
      sfile = inputDir + setname + axislet + ext
      if os.path.exists(sfile):
         copylist.append(sfile)

   for ext in oneAxisHeaders:
      sfile = inputDir + setname + axislet + ext
      if os.path.exists(sfile):
         try:
            unameOut += runcmd('header ' + sfile)
         except:
            warning('Failed to run header on ' + sfile)

   for ext in oneAxisThumbs:
      useExt = ext
      thumbRoot = inputDir + setname + axislet

      # For a montage, get a shot of the preali or ali if possible
      if typeInd == TOMO and ext[1:] in stackExts and viewType and viewType == 'Montage':
         for typeExt in stdTypeExts:
            if os.path.exists(thumbRoot + datasetFilename('.preali', typext = typeExt)):
               useExt = datasetFilename('.preali', typext = typeExt)
               break
            elif os.path.exists(thumbRoot + datasetFilename('.ali', typext = typeExt)):
               useExt = datasetFilename('.ali', typext = typeExt)
               break

      sfile = thumbRoot + useExt
      makeThumbnail(sfile)

   for ext in oneAxisCopyHeads:
      sfile = inputDir + setname + axislet + ext
      copyHeader(sfile)

   axislet = 'b'

# Add single files to copy list if they exist
for sfile in singleFiles:
   sfile = inputDir + sfile
   if os.path.exists(sfile):
      copylist.append(sfile)

# Get headers of single files if they exist.
# They may have spaces in path so run header with standard input
for sfile in singleHeaders:
   if typeInd != BATCH:
      sfile = inputDir + sfile
   if os.path.exists(sfile):
      try:
         unameOut += runcmd('header -StandardInput', ['InputFile ' + sfile])
      except:
         warning('Failed to run header on ' + sfile)

# Get thumbnails, without adding input directory since join files can have abs paths
for sfile in singleThumbs:
   makeThumbnail(sfile)

# Add them all to the tar list, or copy them to other directory if needed and add to
# tar list only if copy succeeds
if tempdir == '.':
   tarlist += copylist
else:
   for sfile in copylist:
      sfile.replace('\\', '/')
      try:
         shutil.copy2(sfile, tempdir)
         tarlist.append(os.path.basename(sfile))
      except:
         warning("Error copying " + sfile + " to " + tempdir)

# Write uname.out at last
stripAndWrite(os.path.join(tempdir, unameFile), unameOut)

# Tar the files
outFile = outputFile
if tempdir != '.':
   os.chdir(tempdir)
   outFile = os.path.join('..', outputFile)

try:
   tarFile = tarfile.open(outFile, 'w|gz')
   for sfile in tarlist:
      try:
         tarFile.add(sfile)
      except:
         warning("Error adding " + sfile + " to the tar file")
   tarFile.close()
except:
   exitError('Opening or writing the tar file')

# Clean up the com dir
if tempdir != '.':
   prnstr("Snapshot done, placed in " + os.path.join(writeableDir, outputFile))
   os.chdir('..')
   try:
      shutil.rmtree(os.path.basename(tempdir.replace('\\', '/')))
   except:
      warning('Failed to remove temporary directory ' + tempdir)
else:
   prnstr("Snapshot done, placed in " + outputFile)
   try:
      shutil.rmtree(os.path.basename(comdir))
   except:
      warning('Failed to remove temporary com file directory ' + comdir)
   removeWarn(unameFile)
   removeWarn(lslrtFile)
   for log in copylogs:
      removeWarn(log)
   for thumb in thumbnails:
      removeWarn(thumb)
   for heads in headerCopies:
      removeWarn(heads)

sys.exit(0)

