text: #! /usr/bin/env python
  
  """This module contains functions for creating Mirage-formatted source
  catalogs
  
  maybe i should change self.ra to self._ra and same with self.dec, to encourage
  people to use the get_ra() and get_dec() functions
  
  Right now create_table returns a table, which is fine, but would also be nice
  to be able to do:
  
  tab = PointSourceCatalog(stuff)
  tab_ap_format = tab.create_table()
  tab.save() - and have tab.save save the tab_ap_format table
  
  
  Maybe:
  tab = PointSourceCatalog(stuff)
  tab.create_table()  # creates tab.astropy_table or something like that
  tab.save()
  
  """
  
  import copy
  import logging
  import os
  
  from astropy.io import ascii
  from astropy.table import Table, Column
  import numpy as np
  
  from mirage.logging import logging_functions
  from mirage.utils.constants import LOG_CONFIG_FILENAME, STANDARD_LOGFILE_NAME
  from mirage.utils.utils import standardize_filters, make_mag_column_names
  
  TSO_GRISM_INDEX = 99999
  
  classdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))
  log_config_file = os.path.join(classdir, 'logging', LOG_CONFIG_FILENAME)
  logging_functions.create_logger(log_config_file, STANDARD_LOGFILE_NAME)
  
  
  class PointSourceCatalog():
      def __init__(self, ra=[], dec=[], x=[], y=[], starting_index=1, niriss_ghost_stamp=[]):
          """Initialize the point source catalog. Users can enter lists of RA and Dec values
          or x and y values for source potisions
  
          Parameters
          ----------
          ra : list
              List of RA values. Provide ra and dec or x and y.
  
          dec : list
              List of Dec values. Provide ra and dec or x and y.
  
          x : list
              List of detector x position values. Provide ra and dec or
              x and y.
  
          y : list
              List of detector y position values. Provide ra and dec or
              x and y.
  
          starting_index : int
              Index number to use for the first source in the catalog.
              Source indexes will increase from this value. If you are
              creating multiple catalogs, this parameter can be used to
              assure that each source in all catalogs has a unique source
              number.
  
          niriss_ghost_stamp : list
              List of fits files to use for ghost sources to be added.
              If left empty but ghost sources are to be added, Mirage will
              fall back to using the default fits files.
          """
          self._starting_index = starting_index
          self._niriss_ghost_stamp = niriss_ghost_stamp
  
          # Make sure we are working with numpy arrays
          ra = np.array(ra)
          dec = np.array(dec)
          x = np.array(x)
          y = np.array(y)
  
          if len(ra) != len(dec):
              raise ValueError(("WARNING: inconsistent length between RA and Dec inputs."))
          if len(x) != len(y):
              raise ValueError(("WARNING: inconsistent length between x and y inputs."))
          if len(ra) > 0 and len(x) > 0:
              raise ValueError(("WARNING: Provide either RA and Dec values, or x and y values."))
  
          if len(ra) > 0:
              self._ra = ra
              self._dec = dec
          else:
              self._ra = x
              self._dec = y
          self.magnitudes = {}
  
          # Determine the units for the location fields. All that Mirage needs to know is whether
          # the units are pixels or not. Degrees vs hour angle is not important at this point.
          if len(ra) > 0:
              self._location_units = 'position_RA_Dec'
          if len(x) > 0:
              self._location_units = 'position_pixels'
  
      def add_catalog(self, catalog_to_add, magnitude_fill_value=99.):
          """Add a catalog to the current catalog instance"""
          # If the the source positions in the two catalogs have different units, then the catalogs
          # can't be combined.
          logger = logging.getLogger('mirage.catalogs.catalog_generator.add_catalog')
  
          if self._location_units != catalog_to_add.location_units:
              raise ValueError("WARNING: Sources in the catalogs do not have matching units (RA/Dec or x/y)")
  
          # Check the magnitude system from each and make sure they match
          current_mag_labels = list(self.magnitudes.keys())
          new_mag_labels = list(catalog_to_add.magnitudes.keys())
          mag_sys = self.magnitudes[current_mag_labels[0]][0]
          if mag_sys != catalog_to_add.magnitudes[new_mag_labels[0]][0]:
              logger.error("Magnitude systems of the two catalogs do not match. Cannot combine.")
  
          # Get the length of the two catalogs
          current_length = len(self._ra)
          new_length = len(catalog_to_add._ra)
  
          # Combine location columns
          self._ra = np.append(self._ra, catalog_to_add._ra)
          self._dec = np.append(self._dec, catalog_to_add._dec)
  
          # Now we need to compare magnitude columns. Columns common to both catalogs can be
          # combined. Columns not common will have to have fill values added so that everything
          # is the same length
          orig_current_mag_labels = copy.deepcopy(current_mag_labels)
          current_mag_labels.extend(new_mag_labels)
          mag_label_set = set(current_mag_labels)
          for label in mag_label_set:
              if ((label in orig_current_mag_labels) and (label not in new_mag_labels)):
                  fill = [magnitude_fill_value] * new_length
                  self.magnitudes[label][1] = np.append(self.magnitudes[label][1], fill)
              if ((label not in orig_current_mag_labels) and (label in new_mag_labels)):
                  fill = [magnitude_fill_value] * current_length
                  self.magnitudes[label] = [mag_sys, np.append(fill, catalog_to_add.magnitudes[label][1])]
              if ((label in orig_current_mag_labels) and (label in new_mag_labels)):
                  self.magnitudes[label][1] = np.append(self.magnitudes[label][1],
                                                        catalog_to_add.magnitudes[label][1])
  
          # Update the catalog table if it already exists, so that it is consistent
          self.create_table()
  
      def add_magnitude_column(self, magnitude_list, magnitude_system='abmag', instrument='', filter_name='', column_name=''):
          """Add a list of magnitudes to the catalog
  
          Parameters
          ----------
          magnitude_list : list
              Magnitude values corresponding to the input sources
  
          magnitude_system : str
              'abmag', 'vegamag', or 'stmag'
  
          instrument : str
              Instrument name
  
          filter_name : str
              Filter name (e.g. 'F090W')
  
          column_name : str
              Name to use for the column name. If this is provided, it will override
              the instrument and filter_name inputs. (e.g. 'nircam_f090w_wlp8_magnitude')
          """
          # Force magnitude list to be a numpy array
          magnitude_list = np.array(magnitude_list)
  
          # Make sure instrument and filter are allowed values
          if column_name == '':
              instrument = instrument.lower()
              filter_name = standardize_filters(instrument, [filter_name.upper()])[0]
              self.filter_check(instrument, filter_name)
  
              # Create the string for the column header
              if instrument == '' or filter_name == '':
                  header = 'magnitude'
              else:
                  header = make_mag_column_names(instrument, [filter_name])[0]
          else:
              header = column_name
  
          # Get a list of magnitude_system for the existing catalog. No mixing of
          # magnitude systems is currently allowed.
          keys = self.magnitudes.keys()
          if len(keys) > 0:
              current_mag_sys = self.magnitudes[list(keys)[0]][0]
          else:
              current_mag_sys = magnitude_system
  
          # Make sure this is not a duplicate column
          if header in self.magnitudes.keys():
              raise ValueError(("WARNING: {} entry already exists in catalog. No duplicate entries allowed."
                                .format(header)))
          # Make sure the magnitude system is consistent
          elif magnitude_system != current_mag_sys:
                  raise ValueError(("WARNING: only a single magnitude system is allowed per catalog. "
                                    "Current catalog is using {}, while the new entry is {}."
                                    .format(current_mag_sys, magnitude_system)))
          else:
              self.magnitudes[header] = [magnitude_system, magnitude_list]
  
          # Update the catalog table
          self.create_table()
  
      def filter_check(self, inst_name, filt_name):
          """Make sure the requested instrument/filter pair is valid
          """
          if inst_name == 'nircam':
              filter_file = 'nircam_filter_pupil_pairings.list'
          elif inst_name == 'niriss':
              filter_file = 'niriss_dual_wheel_list.txt'
  
          if inst_name in ['nircam', 'niriss']:
              filter_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../config/',
                                                              filter_file))
              filter_table = ascii.read(filter_file_path)
              if filt_name.upper() not in filter_table['filter']:
                  raise ValueError("WARNING: {} is not a valid filter for {}.".format(filt_name,
                                                                                      inst_name.upper()))
          if inst_name == 'fgs':
              if filt_name.lower() not in ['guider1', 'guider2']:
                  raise ValueError("WARNING: {} is not a valid filter for FGS.".format(filt_name))
  
      def instrument_check(self, inst_name):
          """Only certain instrument values are allowed
          NOT USED - so that we can return J, H, K from searches
          """
          if inst_name not in ['nircam', 'niriss', 'fgs']:
              raise ValueError("WARNING: {} is not a valid instrument.".format(inst_name))
  
      def get_magnitudes(self, key):
          """Return the magnitude list (but not mag_sys) associated with the given key
  
          Parameters
          ----------
          key : str
              Column name in the catalog (e.g. 'nircam_f090w_magnitudes')
          """
          logger = logging.getLogger('mirage.catalogs.catalog_generator.get_magnitudes')
  
          try:
              return self.magnitudes[key][1]
          except KeyError:
              logger.error("WARNING: No {} magnitude column present.".format(key))
  
      @property
      def location_units(self):
          """Return RA values from catalog"""
          return self._location_units
  
      @property
      def ra(self):
          """Return RA values from catalog"""
          if self._location_units == 'position_RA_Dec':
              return self._ra
          else:
              return []
  
      @property
      def dec(self):
          """Return Dec values from catalog"""
          if self._location_units == 'position_RA_Dec':
              return self._dec
          else:
              return []
  
      @property
      def x(self):
          """Return x values from catalog"""
          if self._location_units == 'position_pixels':
              return self._ra
          else:
              return []
  
      @property
      def y(self):
          """Return y values from catalog"""
          if self._location_units == 'position_pixels':
              return self._dec
          else:
              return []
  
      def create_table(self):
          """Create an astropy table containing the catalog
          """
          tab = create_basic_table(self._ra, self._dec, self.magnitudes, self._location_units,
                                   minimum_index=self._starting_index, niriss_ghost_stamp=self._niriss_ghost_stamp)
  
          # Make sure there are at least 4 comment lines at the top
          self.table = pad_table_comments(tab)
  
      def save(self, output_name):
          """Write out the catalog to an ascii file
  
          Parameters
          ----------
          something
          """
          self.table.write(output_name, format='ascii', overwrite=True)
  
  
  class GalaxyCatalog(PointSourceCatalog):
      def __init__(self, ra=[], dec=[], x=[], y=[], ellipticity=[], radius=[], sersic_index=[],
                   position_angle=[], radius_units='arcsec', starting_index=1, niriss_ghost_stamp=[]):
          """Create instance of a galaxy catalog
          """
          # Add location information
          PointSourceCatalog.__init__(self, ra=ra, dec=dec, x=x, y=y, starting_index=starting_index)
  
          # Add galaxy-specific information
          self._morphology = {'radius': radius, 'ellipticity': ellipticity, 'sersic_index': sersic_index,
                              'pos_angle': position_angle}
  
          self._ellipticity = ellipticity
          self._pos_angle = position_angle
          self._radius = radius
          self._sersic_index = sersic_index
          self._niriss_ghost_stamp = niriss_ghost_stamp
  
          if radius_units not in ['arcsec', 'pixels']:
              raise ValueError("WARNING: Galaxy radii must be in units of 'arcsec' or 'pixels'.")
          self._radius_units = radius_units
  
      def create_table(self):
          """Create an astropy table containing the catalog
          """
          tab = create_basic_table(self._ra, self._dec, self.magnitudes, self.location_units,
                                   minimum_index=self._starting_index, niriss_ghost_stamp=self._niriss_ghost_stamp)
  
          # Add morphology columns
          for key in self._morphology:
              values = self._morphology[key]
              col = Column(values, name=key)
              tab.add_column(col, index=3)
  
          # Add magnitude system and position units as metadata
          tab.meta['comments'].append("radius_{}".format(self.radius_units))
  
          # Make sure there are at least 4 comment lines at the top
          self.table = pad_table_comments(tab)
  
      @property
      def ellipticity(self):
          """Return Dec values from catalog"""
          return self._ellipticity
  
      @property
      def position_angle(self):
          """Return Dec values from catalog"""
          return self._pos_angle
  
      @property
      def radius(self):
          """Return Dec values from catalog"""
          return self._radius
  
      @property
      def sersic_index(self):
          """Return Dec values from catalog"""
          return self._sersic_index
  
      @property
      def radius_units(self):
          """Return Dec values from catalog"""
          return self._radius_units
  
      @property
      def morphology(self):
          """Return Dec values from catalog"""
          return self._morphology
  
  
  class ExtendedCatalog(PointSourceCatalog):
      def __init__(self, filenames=[], ra=[], dec=[], x=[], y=[], position_angle=[], starting_index=1,
                   niriss_ghost_stamp=[], corresponding_source_index_for_ghost=[]):
          # Add location information
          PointSourceCatalog.__init__(self, ra=ra, dec=dec, x=x, y=y, starting_index=starting_index,
                                      niriss_ghost_stamp=niriss_ghost_stamp)
  
          # Add extended source-specific information
          self._filenames = filenames
          self._pos_angle = position_angle
          self._corresponding_source_index_for_ghost = corresponding_source_index_for_ghost
  
          if len(self._filenames) != len(self._ra):
              raise ValueError(("Lengths of stamp filenames list and RA/Dec or x/y lists are not equal!"))
  
          if len(self._pos_angle) != len(self._ra):
              raise ValueError(("Lengths of position angle list and RA/Dec or x/y lists are not equal!"))
  
      def create_table(self):
          """Create an astropy table containing the catalog
          """
          tab = create_basic_table(self._ra, self._dec, self.magnitudes, self._location_units,
                                   minimum_index=self._starting_index, niriss_ghost_stamp=self._niriss_ghost_stamp)
  
          # Add the filename column
          file_col = Column(self._filenames, name='filename')
          tab.add_column(file_col, index=3)
  
          # Add the position angle column
          pa_col = Column(self._pos_angle, name='pos_angle')
          tab.add_column(pa_col)
  
          # Add the column that lists the index for the original target that
          # is the source for the ghost target. For non-ghosts, this column should
          # have a value of 0.
          if len(self._corresponding_source_index_for_ghost) > 0:
              src_index_col = Column(self._corresponding_source_index_for_ghost, name='corresponding_source_index_for_ghost')
              tab.add_column(src_index_col)
  
          # Make sure there are at least 4 comment lines at the top
          self.table = pad_table_comments(tab)
  
      @property
      def filename(self):
          """Return Dec values from catalog"""
          return self._filenames
  
      @property
      def position_angle(self):
          """Return Dec values from catalog"""
          return self._pos_angle
  
  
  class MovingPointSourceCatalog(PointSourceCatalog):
      def __init__(self, ra=[], dec=[], x=[], y=[], ra_velocity=[], dec_velocity=[], x_velocity=[],
                   y_velocity=[], ephemeris_file=[], starting_index=1, niriss_ghost_stamp=[]):
  
          # If ephemeris files are provided, ra/dec/x/y as well as velocities are not needed
          if len(ra) == 0 and len(x) == 0:
              if len(ephemeris_file) > 0:
                  ra = [None] * len(ephemeris_file)
                  dec = [None] * len(ephemeris_file)
              else:
                  raise ValueError("Object location must be provided via ra/dec, x/y, or ephemeris_file keywords.")
  
          # If the user provides ephemeris files, they shouldn't need to provide any
          # other position or velocity information.
          if len(ephemeris_file) > 0:
              if len(ra) == 0 and len(x) == 0:
                  ra = [None] * len(ephemeris_file)
                  dec = [None] * len(ephemeris_file)
              if len(ra_velocity) == 0 and len(x_velocity) == 0:
                  ra_velocity = [None] * len(ephemeris_file)
                  dec_velocity = [None] * len(ephemeris_file)
  
          # Add location information
          PointSourceCatalog.__init__(self, ra=ra, dec=dec, x=x, y=y, starting_index=starting_index,
                                      niriss_ghost_stamp=niriss_ghost_stamp)
  
          if len(ra_velocity) > 0 and len(x_velocity) > 0:
              raise ValueError(("WARNING: Provide either RA and Dec velocities, or x and y "
                                "velocities, but not both."))
  
          if len(ra_velocity) == 0 and len(x_velocity) == 0 and len(ephemeris_file) == 0:
              raise ValueError(("Object velocities must be specified using either ra/dec_velocity, x/y_velocity "
                                "or ephemeris_file keywords."))
  
          if len(ra_velocity) > 0:
              self._ra_velocity = np.array(ra_velocity)
              self._dec_velocity = np.array(dec_velocity)
              self._velocity_units = 'velocity_RA_Dec'
          elif len(x_velocity) > 0:
              self._ra_velocity = np.array(x_velocity)
              self._dec_velocity = np.array(y_velocity)
              self._velocity_units = 'velocity_pixels'
          else:
              self._ra_velocity = np.repeat(None, len(self._ra))
              self._dec_velocity = np.repeat(None, len(self._ra))
              self._velocity_units = 'velocity_RA_Dec'
  
          if len(ephemeris_file) > 0:
              self._ephemeris_file = np.array(ephemeris_file)
          else:
              self._ephemeris_file = np.repeat('None', len(self._ra_velocity))
  
          # If ephemeris files are given, modify the position and velocity
          # values such that they are None in rows where there is an ephemeris file
          eph_set = set(self._ephemeris_file)
  
          if eph_set != set(['None']):
              print(("Ephemeris information overrides provided source positions and velocities. "
                    "Setting these to NaN in cases where an ephemeris file is given, in order "
                    "to avoid confusion."))
              eph_files = self._ephemeris_file != 'None'
  
              self._ra[eph_files] = None
              self._dec[eph_files] = None
              self._ra_velocity[eph_files] = None
              self._dec_velocity[eph_files] = None
  
  
      def create_table(self):
          tab = create_basic_velocity_table(self._ra, self._dec, self.magnitudes, self._location_units,
                                            self._ra_velocity, self._dec_velocity, self._velocity_units,
                                            self._ephemeris_file, minimum_index=self._starting_index,
                                            niriss_ghost_stamp=self._niriss_ghost_stamp)
  
          # Make sure there are at least 4 comment lines at the top
          self.table = pad_table_comments(tab)
  
      @property
      def dec_velocity(self):
          """Return Dec values from catalog"""
          return self._dec_velocity
  
      @property
      def ra_velocity(self):
          """Return Dec values from catalog"""
          return self._ra_velocity
  
      @property
      def velocity_units(self):
          """Return Dec values from catalog"""
          return self._velocity_units
  
  
  class MovingSersicCatalog(GalaxyCatalog, MovingPointSourceCatalog):
      def __init__(self, ra=[], dec=[], x=[], y=[], ra_velocity=[], dec_velocity=[], x_velocity=[],
                   y_velocity=[], ellipticity=[], radius=[], sersic_index=[], position_angle=[],
                   radius_units='arcsec', ephemeris_file=[], starting_index=1, niriss_ghost_stamp=[]):
          # Add location information
          GalaxyCatalog.__init__(self, ra=ra, dec=dec, x=x, y=y, ellipticity=ellipticity, radius=radius,
                                 sersic_index=sersic_index, position_angle=position_angle,
                                 radius_units='arcsec', starting_index=starting_index,
                                 niriss_ghost_stamp=niriss_ghost_stamp)
          MovingPointSourceCatalog.__init__(self, ra=ra, dec=dec, x=x, y=y, ra_velocity=ra_velocity,
                                            dec_velocity=dec_velocity, x_velocity=x_velocity,
                                            y_velocity=y_velocity, ephemeris_file=ephemeris_file,
                                            starting_index=starting_index)
  
      def create_table(self):
          tab = create_basic_velocity_table(self._ra, self._dec, self.magnitudes, self._location_units,
                                            self._ra_velocity, self._dec_velocity, self._velocity_units,
                                            self._ephemeris_file, minimum_index=self._starting_index,
                                            niriss_ghost_stamp=self._niriss_ghost_stamp)
          # Add morphology columns
          for key in self._morphology:
              values = self._morphology[key]
              col = Column(values, name=key)
              tab.add_column(col, index=3)
  
          # Add magnitude system and position units as metadata
          tab.meta['comments'].append("radius_{}".format(self._radius_units))
  
          # Make sure there are at least 4 comment lines at the top
          self.table = pad_table_comments(tab)
  
  
  class MovingExtendedCatalog(ExtendedCatalog, MovingPointSourceCatalog):
      def __init__(self, ra=[], dec=[], x=[], y=[], ra_velocity=[], dec_velocity=[], x_velocity=[],
                   y_velocity=[], filenames=[], position_angle=[], ephemeris_file=[], starting_index=1,
                   niriss_ghost_stamp=[]):
          # Add location information
          ExtendedCatalog.__init__(self, ra=ra, dec=dec, x=x, y=y, filenames=filenames,
                                   position_angle=position_angle, starting_index=starting_index,
                                   niriss_ghost_stamp=niriss_ghost_stamp)
          MovingPointSourceCatalog.__init__(self, ra=ra, dec=dec, x=x, y=y, ra_velocity=ra_velocity,
                                            dec_velocity=dec_velocity, x_velocity=x_velocity,
                                            y_velocity=y_velocity, ephemeris_file=ephemeris_file,
                                            starting_index=starting_index)
  
      def create_table(self):
          tab = create_basic_velocity_table(self._ra, self._dec, self.magnitudes, self._location_units,
                                            self._ra_velocity, self._dec_velocity, self._velocity_units,
                                            self._ephemeris_file, minimum_index=self._starting_index,
                                            niriss_ghost_stamp=self._niriss_ghost_stamp)
          # Add filename column
          file_col = Column(self._filenames, name='filename')
          tab.add_column(file_col, index=1)
  
          # Add position_angle column
          pa_col = Column(self._pos_angle, name='pos_angle')
          tab.add_column(pa_col, index=6)
  
          # Make sure there are at least 4 comment lines at the top
          self.table = pad_table_comments(tab)
  
  
  class NonSiderealCatalog(MovingPointSourceCatalog):
      def __init__(self, ra=[], dec=[], x=[], y=[], ra_velocity=[], dec_velocity=[], x_velocity=[],
                   y_velocity=[], object_type=[], ephemeris_file=[], starting_index=1, niriss_ghost_stamp=[]):
          # Add location information
          MovingPointSourceCatalog.__init__(self, ra=ra, dec=dec, x=x, y=y, ra_velocity=ra_velocity,
                                            dec_velocity=dec_velocity, x_velocity=x_velocity,
                                            y_velocity=y_velocity, ephemeris_file=ephemeris_file,
                                            niriss_ghost_stamp=niriss_ghost_stamp)
  
          # List of the type of sources in the non-sidereal catalog
          valid_objects = ['pointSource', 'galaxies', 'extended']
          for obj in object_type:
              if obj not in valid_objects:
                  raise ValueError(("WARNING: object_type list members must be one of: {}"
                                    .format(obj, valid_objects)))
          self.object_type = object_type
  
      def create_table(self):
          """Create an astropy table containing the catalog
          """
          tab = create_basic_velocity_table(self._ra, self._dec, self.magnitudes, self._location_units,
                                            self._ra_velocity, self._dec_velocity, self._velocity_units,
                                            self._ephemeris_file, minimum_index=self._starting_index,
                                            niriss_ghost_stamp=self._niriss_ghost_stamp)
          obj_col = Column(self.object_type, name='object')
          tab.add_column(obj_col, index=1)
  
          # Make sure there are at least 4 comment lines at the top
          self.table = pad_table_comments(tab)
  
  
  class ImagingTSOCatalog(PointSourceCatalog):
      def __init__(self, ra=[], dec=[], x=[], y=[], lightcurve_file=[]):
          """Create instance of an imaging TSO catalog
          """
          # Add location information
          PointSourceCatalog.__init__(self, ra=ra, dec=dec, x=x, y=y)
  
          # Add TSO-specific information
          self._lightcurve_file = lightcurve_file
  
      def create_table(self):
          """Create an astropy table containing the catalog
          """
          tab = create_basic_table(self._ra, self._dec, self.magnitudes, self.location_units,
                                   minimum_index=TSO_GRISM_INDEX)
  
          # Add the lightcurve filename column
          lc_col = Column(self._lightcurve_file, name='lightcurve_file')
          tab.add_column(lc_col, index=3)
  
          # Make sure there are at least 4 comment lines at the top
          self.table = pad_table_comments(tab)
  
      @property
      def lightcurve_file(self):
          """Return lightcurve filenames"""
          return self._lightcurve_file
  
  
  class GrismTSOCatalog(PointSourceCatalog):
      def __init__(self, ra=[], dec=[], x=[], y=[], semimajor_axis=[], orbital_inclination=[],
                   eccentricity=[], orbital_period=[], longitude_of_periastron=[], limb_dark_model=[],
                   limb_dark_coeffs=[], time_units=[], start_time=[], end_time=[], inferior_conj=[],
                   transmission_spectrum=[]):
          """Create instance of a grism TSO catalog
          """
          # Add location information
          PointSourceCatalog.__init__(self, ra=ra, dec=dec, x=x, y=y)
  
          # Add TSO-specific information
          self._semimajor_axis = np.array(semimajor_axis)
          self._orbital_inclination = np.array(orbital_inclination)
          self._eccentricity = np.array(eccentricity)
          self._orbital_period = np.array(orbital_period)
          self._longitude_of_periastron = np.array(longitude_of_periastron)
          self._limb_dark_model = np.array(limb_dark_model)
          self._time_units = np.array(time_units)
          self._start_time = np.array(start_time)
          self._end_time = np.array(end_time)
          self._inferior_conj = np.array(inferior_conj)
          self._transmission_spectrum = np.array(transmission_spectrum)
  
          # Limb darkening coefficients should be a string of comma-separated
          # numbers
          str_limb_dark_coeffs = []
          for entry in limb_dark_coeffs:
              str_limb_dark_coeffs.append(str(entry).replace('[', '').replace(']', ''))
          self._limb_dark_coeffs = np.array(str_limb_dark_coeffs)
  
      def create_table(self):
          """Create an astropy table containing the catalog
          """
          tab = create_basic_table(self._ra, self._dec, self.magnitudes, self.location_units,
                                   minimum_index=TSO_GRISM_INDEX, niriss_ghost_stamp=self._niriss_ghost_stamp)
  
          # Add the lightcurve filename column
          semimajor_col = Column(self._semimajor_axis, name='Semimajor_axis_in_stellar_radii')
          tab.add_column(semimajor_col, index=3)
  
          orb_inc_col = Column(self._orbital_inclination, name='Orbital_inclination_deg')
          tab.add_column(orb_inc_col, index=4)
  
          ecc_col = Column(self._eccentricity, name='Eccentricity')
          tab.add_column(ecc_col, index=5)
  
          pastron_col = Column(self._longitude_of_periastron, name='Longitude_of_periastron')
          tab.add_column(pastron_col, index=6)
  
          limb_model_col = Column(self._limb_dark_model, name='Limb_darkening_model')
          tab.add_column(limb_model_col, index=7)
  
          limb_coeff_col = Column(self._limb_dark_coeffs, name='Limb_darkening_coeffs')
          tab.add_column(limb_coeff_col, index=8)
  
          time_unit_col = Column(self._time_units, name='Time_units')
          tab.add_column(time_unit_col, index=9)
  
          start_col = Column(self._start_time, name='Start_time')
          tab.add_column(start_col, index=10)
  
          end_col = Column(self._end_time, name='End_time')
          tab.add_column(end_col, index=11)
  
          inf_conj_col = Column(self._inferior_conj, name='Time_of_inferior_conjunction')
          tab.add_column(inf_conj_col, index=12)
  
          orb_per_col = Column(self._orbital_period, name='Orbital_period')
          tab.add_column(orb_per_col, index=13)
  
          spec_col = Column(self._transmission_spectrum, name='Transmission_spectrum')
          tab.add_column(spec_col, index=14)
  
          # Make sure there are at least 4 comment lines at the top
          self.table = pad_table_comments(tab)
  
      @property
      def semimajor_axis(self):
          return self._semimajor_axis
  
      @property
      def orbital_inclination(self):
          return self._orbital_inclination
  
      @property
      def eccentricity(self):
          return self._eccentricity
  
      @property
      def longitude_of_periastron(self):
          return self._longitude_of_periastron
  
      @property
      def limb_dark_model(self):
          return self._limb_dark_model
  
      @property
      def limb_dark_coeffs(self):
          return self._limb_dark_coeffs
  
      @property
      def time_units(self):
          return self._time_units
  
      @property
      def start_time(self):
          return self._start_time
  
      @property
      def end_time(self):
          return self._end_time
  
      @property
      def inferior_conj(self):
          return self._inferior_conj
  
      @property
      def orbital_period(self):
          return self._orbital_period
  
      @property
      def transmission_spectrum(self):
          return self._transmission_spectrum
  
  
  def add_ephemeris_column(input_table, ephemeris_files):
      """Add a column containing ephemeris filenames
  
      Arguments
      ---------
      input_table : astropy.table.Table
          Input catalog
  
      ephemeris_files : list
          Ephemeris filenames to be added
  
      Returns
      -------
      input_table : astropy.table.Table
          Input catalog with ephemeris_file column added
      """
      eph_col = Column(ephemeris_files, name='ephemeris_file')
      input_table.add_column(eph_col, index=6)
      return input_table
  
  
  def add_velocity_columns(input_table, ra_velocities, dec_velocities, velocity_units):
      """Add RA/Dec velocity columns to table of source information
  
      Arguments
      ---------
      input_table : astropy.table.Table
          Input catalog
  
      ra_velocities : list
          RA velocity values to be added (arcsec/hour)
  
      dec_velocities : list
          Dec velocity values to be added (arcsec/hour)
  
      velocity_units : str
          Velocity units. Will be stored in a comment in the catalog
  
      Returns
      -------
      input_table : astropy.table.Table
          Input catalog with ephemeris_file column added
  
      """
      ra_vel_col = Column(ra_velocities, name='x_or_RA_velocity')
      dec_vel_col = Column(dec_velocities, name='y_or_Dec_velocity')
      input_table.add_columns([ra_vel_col, dec_vel_col], indexes=[3, 3])
      input_table.meta['comments'].append(velocity_units)
      return input_table
  
  
  def cat_from_file(filename, catalog_type='point_source'):
      """Read in a Mirage-formatted ascii catalog file and place into an
      instance of a catalog object
  
      Parameters
      ----------
      filename : str
          Name of ascii catalog to be read in
  
      catalog_type : str
          Type of source catalog. Allowed values are:
          point_source, galaxy, extended, moving_point_source, moving_sersic,
          moving_extended, non_sidereal
  
      Returns
      -------
      catalog_object : mirage.catalogs.catalog_generator object
      """
      catalog_type = catalog_type.lower()
      allowed_types = ['point_source', 'galaxy', 'extended', 'moving_point_source',
                       'moving_sersic', 'moving_extended', 'non_sidereal']
      if catalog_type not in allowed_types:
          raise ValueError(("Input catalog type {} is not one of the allowed types: {}"
                           .format(catalog_type, allowed_types)))
  
      cat_table = ascii.read(filename)
  
      if 'position_pixels' in cat_table.meta['comments'][0:4]:
          xpos = 'x'
          ypos = 'y'
      else:
          xpos = 'ra'
          ypos = 'dec'
  
      magnitude_columns = [col for col in cat_table.colnames if 'magnitude' in col]
  
      # Point Source catalog
      if catalog_type == 'point_source':
          catalog_object = PointSourceCatalog(**{xpos: cat_table['x_or_RA']}, **{ypos: cat_table['y_or_Dec']})
          for magcol in magnitude_columns:
              instrument, filtername = get_inst_filter_from_colname(magcol)
              catalog_object.add_magnitude_column(cat_table[magcol], instrument=instrument,
                                                  filter_name=filtername)
  
      # Galaxy catalog
      elif catalog_type == 'galaxy':
          radius_units = 'arcsec'
          if 'radius_pixels' in cat_table.meta['comments'][0:4]:
              radius_units = 'pixels'
          catalog_object = GalaxyCatalog(**{xpos: cat_table['x_or_RA']}, **{ypos: cat_table['y_or_Dec']},
                                         ellipticity=cat_table['ellipticity'], radius=cat_table['radius'],
                                         sersic_index=cat_table['sersic_index'], position_angle=cat_table['pos_angle'],
                                         radius_units=radius_units)
          for magcol in magnitude_columns:
              instrument, filtername = get_inst_filter_from_colname(magcol)
              catalog_object.add_magnitude_column(cat_table[magcol], instrument=instrument,
                                                  filter_name=filtername)
  
      # Extended catalog
      elif catalog_type == 'extended':
          catalog_object = ExtendedCatalog(**{xpos: cat_table['x_or_RA']}, **{ypos: cat_table['y_or_Dec']},
                                           filenames=cat_table['filename'], position_angle=cat_table['pos_angle'])
          for magcol in magnitude_columns:
              instrument, filtername = get_inst_filter_from_colname(magcol)
              catalog_object.add_magnitude_column(cat_table[magcol], instrument=instrument,
                                                  filter_name=filtername)
  
      # Moving point source catalog
      elif catalog_type == 'moving_point_source':
          velocity_units = 'velocity_RA_Dec'
          xvel = 'ra_velocity'
          yvel = 'dec_velocity'
          if 'velocity_pixels' in cat_table.meta['comments'][0:4]:
              velocity_units = 'velocity_pixels'
              xvel = 'x_velocity'
              yvel = 'y_velocity'
          catalog_object = MovingPointSourceCatalog(**{xpos: cat_table['x_or_RA']},
                                                    **{ypos: cat_table['y_or_Dec']},
                                                    **{xvel: cat_table['x_or_RA_velocity']},
                                                    **{yvel: cat_table['y_or_Dec_velocity']})
          for magcol in magnitude_columns:
              instrument, filtername = get_inst_filter_from_colname(magcol)
              catalog_object.add_magnitude_column(cat_table[magcol], instrument=instrument,
                                                  filter_name=filtername)
  
      # Moving sersic catalog
      elif catalog_type == 'moving_sersic':
          velocity_units = 'velocity_RA_Dec'
          xvel = 'ra_velocity'
          yvel = 'dec_velocity'
          if 'velocity_pixels' in cat_table.meta['comments'][0:4]:
              velocity_units = 'velocity_pixels'
              xvel = 'x_velocity'
              yvel = 'y_velocity'
          radius_units = 'arcsec'
          if 'radius_pixels' in cat_table.meta['comments'][0:4]:
              radius_units = 'pixels'
          catalog_object = MovingSersicCatalog(**{xpos: cat_table['x_or_RA']},
                                               **{ypos: cat_table['y_or_Dec']},
                                               **{xvel: cat_table['x_or_RA_velocity']},
                                               **{yvel: cat_table['y_or_Dec_velocity']},
                                               ellipticity=cat_table['ellipticity'],
                                               radius=cat_table['radius'],
                                               sersic_index=cat_table['sersic_index'],
                                               position_angle=cat_table['pos_angle'],
                                               radius_units=radius_units)
          for magcol in magnitude_columns:
              instrument, filtername = get_inst_filter_from_colname(magcol)
              catalog_object.add_magnitude_column(cat_table[magcol], instrument=instrument,
                                                  filter_name=filtername)
  
      # Moving extended catalog
      elif catalog_type == 'moving_extended':
          velocity_units = 'velocity_RA_Dec'
          xvel = 'ra_velocity'
          yvel = 'dec_velocity'
          if 'velocity_pixels' in cat_table.meta['comments'][0:4]:
              velocity_units = 'velocity_pixels'
              xvel = 'x_velocity'
              yvel = 'y_velocity'
          catalog_object = MovingExtendedCatalog(**{xpos: cat_table['x_or_RA']},
                                                 **{ypos: cat_table['y_or_Dec']},
                                                 **{xvel: cat_table['x_or_RA_velocity']},
                                                 **{yvel: cat_table['y_or_Dec_velocity']},
                                                 filenames=cat_table['filename'],
                                                 position_angle=cat_table['pos_angle'])
          for magcol in magnitude_columns:
              instrument, filtername = get_inst_filter_from_colname(magcol)
              catalog_object.add_magnitude_column(cat_table[magcol], instrument=instrument,
                                                  filter_name=filtername)
  
      # Non-sidereal catalog
      elif catalog_type == 'non_sidereal':
          velocity_units = 'velocity_RA_Dec'
          xvel = 'ra_velocity'
          yvel = 'dec_velocity'
          if 'velocity_pixels' in cat_table.meta['comments'][0:4]:
              velocity_units = 'velocity_pixels'
              xvel = 'x_velocity'
              yvel = 'y_velocity'
          catalog_object = NonSiderealCatalog(**{xpos: cat_table['x_or_RA']},
                                              **{ypos: cat_table['y_or_Dec']},
                                              **{xvel: cat_table['x_or_RA_velocity']},
                                              **{yvel: cat_table['y_or_Dec_velocity']},
                                              object_type=cat_table['object'])
          for magcol in magnitude_columns:
              instrument, filtername = get_inst_filter_from_colname(magcol)
              catalog_object.add_magnitude_column(cat_table[magcol], instrument=instrument,
                                                  filter_name=filtername)
      return catalog_object
  
  
  def create_basic_table(ra_values, dec_values, magnitudes, location_units, minimum_index=1, niriss_ghost_stamp=[]):
      """Create astropy table containing the basic catalog info
      NOTE THAT THIS IS OUTSIDE CLASSES
      """
      basic_table = Table()
  
      # Add index, filename, RA, Dec or x, y columns
      index_col = Column(np.arange(minimum_index, minimum_index + len(ra_values)), name='index')
      ra_col = Column(ra_values, name='x_or_RA')
      dec_col = Column(dec_values, name='y_or_Dec')
      basic_table.add_columns([index_col, ra_col, dec_col])
  
      # Add magnitude columns
      for key in magnitudes:
          mag_values = magnitudes[key][1]
          mag_sys = magnitudes[key][0]
          mag_column = Column(mag_values, name=key)
          basic_table.add_column(mag_column)
  
      # If filenames for NIRISS ghost sources are provided, add that column
      if len(niriss_ghost_stamp) > 0:
          nis_ghost_col = Column(niriss_ghost_stamp, name='niriss_ghost_stamp')
          basic_table.add_column(nis_ghost_col)
  
      # Add magnitude system and position units as metadata
      basic_table.meta['comments'] = [location_units, mag_sys]
      return basic_table
  
  
  def create_basic_velocity_table(ra_values, dec_values, magnitudes, location_units,
                                  ra_velocities, dec_velocities, velocity_units,
                                  ephemeris_values, minimum_index=1, niriss_ghost_stamp=[]):
      tab = create_basic_table(ra_values, dec_values, magnitudes, location_units, minimum_index=minimum_index,
                               niriss_ghost_stamp=niriss_ghost_stamp)
      tab = add_velocity_columns(tab, ra_velocities, dec_velocities, velocity_units)
      tab = add_ephemeris_column(tab, ephemeris_values)
      return tab
  
  
  def get_inst_filter_from_colname(column_name):
      """Get the instrument and filter name from a magnitude column string
  
      Parameters
      ----------
      column_name : str
          Magnitude column header from catalog (e.g. 'nircam_f444w_magnitude')
  
      Returns
      -------
      instrument : str
          Instrument name
  
      filten_name : str
          Filter name
      """
      parts = column_name.split('_')
      instrument = parts[0].lower()
      if instrument == 'guider':
          filter_name = ''
      else:
          filter_name = parts[1].lower()
      return instrument, filter_name
  
  
  def pad_table_comments(input_table):
      """do it"""
      if len(input_table.meta['comments']) < 4:
          pad = [''] * (4 - len(input_table.meta['comments']))
          input_table.meta['comments'].extend(pad)
      return input_table
  