def output_catalog(self,outfile):
        print("    Saving catalog: ", outfile)

        fobj = open(outfile,'w')

        fobj.write('## Lightcone Catalog File for input geometry: '+self.lightconefile+'\n')
        fobj.write('## Catalog source Directory: '+self.basedir+'\n')
        fobj.write('## Square FOV (arcmin): {:12.6f}'.format(self.delb_arcmin)+'\n')
        fobj.write('## Area (arcmin^2): {:12.6f}'.format(self.delb_arcmin**2)+'\n')
        fobj.write('## Baryonic Mass Lower Limit (Msun) : {:10.5e}'.format(self.mass_limit)+'\n')
        fobj.write('## Assumed Cosmology: '+WMAP7.__str__()+'\n')
        fobj.write('## Creator:  Greg Snyder (STScI) \n')
        fobj.write('## Catalog & Data Release Reference:  Nelson et al. (2015) \n')
        fobj.write('## Catalog & Data Release URL: illustris-project.org/data \n')
        fobj.write('## Column 01: Snapshot number \n')
        fobj.write('## Column 02: Subhalo Index \n')
        fobj.write('## Column 03: RA (degrees) \n')
        fobj.write('## Column 04: DEC (degrees) \n')
        fobj.write('## Column 05: RA (proper kpc at true z) \n')
        fobj.write('## Column 06: DEC (proper kpc at true z) \n')
        fobj.write('## Column 07: RA (proper kpc at inferred z) \n')
        fobj.write('## Column 08: DEC (proper kpc at inferred z) \n')
        fobj.write('## Column 09: True cosmological redshift \n')
        fobj.write('## Column 10: Inferred redshift (includes peculiar v) \n')
        fobj.write('## Column 11: Peculiar redshift; Peculiar Velocity / Speed of Light \n')
        fobj.write('## Column 12: True scale at cosmological z, in kpc/arcsec \n')
        fobj.write('## Column 13: [Mpc] Comoving X in Observer Coordinates \n')
        fobj.write('## Column 14: [Mpc] Comoving Y in Observer Coordinates \n')
        fobj.write('## Column 15: [Mpc] Comoving Z in Observer Coordinates \n')
        fobj.write('## Column 16: [Mpc] True Angular Diameter Distance to observer \n')
        fobj.write('## Column 17: [Mpc] Inferred Angular Diameter Distance to observer \n')
        fobj.write('## Column 18: Snapshot redshift \n')
        fobj.write('## Column 19: Geometrically appropriate redshift at center of this cylinder \n')
        fobj.write('## Column 20: Lightcone cylinder number \n')
        fobj.write('## Column 21: [Msun] Stellar mass within 2X stellar half mass radius\n')
        fobj.write('## Column 22: [Msun] Total gas mass within 2X stellar half mass radius\n')
        fobj.write('## Column 23: [Msun] Total mass of this subhalo (excludes children subhalos) \n')
        fobj.write('## Column 24: [Msun] Total BH mass within 2X stellar half mass radius\n')
        fobj.write('## Column 25: [Msun] Total baryon mass within 2X stellar half mass radius\n')
        fobj.write('## Column 26: [Msun/year] SFR within 2X stellar half mass radius\n')
        fobj.write('## Column 27: [(10^10 Msun/h) / (0.978 Gyr/h)] Total BH accretion rate within subhalo\n')
        fobj.write('## Column 28: [Mpc] Camera X in Observer Coordinates (Proper X at z; a transverse coordinate) \n')
        fobj.write('## Column 29: [Mpc] Camera Y in Observer Coordinates (Proper Y at z; a transverse coordinate)\n')
        fobj.write('## Column 30: [Mpc] Camera Z in Observer Coordinates (Proper Z at z; should be almost exactly Column 16)\n')
        fobj.write('## Column 31: [AB Mag] Intrinsic stellar g absolute magnitude (BC03) \n')
        fobj.write('## Column 32: [AB Mag] Intrinsic stellar r absolute magnitude (BC03) \n')
        fobj.write('## Column 33: [AB Mag] Intrinsic stellar i absolute magnitude (BC03) \n')
        fobj.write('## Column 34: [AB Mag] Intrinsic stellar z absolute magnitude (BC03) \n')
        fobj.write('## Column 35: [km/s] Galaxy motion in transverse Camera X direction \n')
        fobj.write('## Column 36: [km/s] Galaxy motion in transverse Camera Y direction \n')
        fobj.write('## Column 37: [km/s] Galaxy motion in line-of-sight Camera Z direction ; the Peculiar Velocity \n')
        fobj.write('## Column 38: [km/s] Cosmological expansion velocity at true z (Column 10 measures Column 37+38)\n')
        fobj.write('## Column 39: [AB Mag] Apparent total rest-frame g-band magnitude (BC03) \n')

        for cylobj in self.cylinder_object_list:
            if cylobj is not None:
                cylobj.print_cylinder(fobj)

        fobj.close()
        return
    def process_lightcone(self,minz=0.0,maxz=20.0,outfile='output.txt'):
        # Initialize values
        cmd_total = 0.0
        cmx = 0.0
        cmy = 0.0
        cmz = 0.0
        
        # Open file for writing
        print("    Opening file for saving: ", outfile)

        fobj = open(outfile,'w')

        fobj.write('## Lightcone Catalog File for input geometry: '+self.lightconefile+'\n')
        fobj.write('## Catalog source directory: '+self.base_dir+'\n')
        fobj.write('## Square FOV (arcmin): {:12.6f}'.format(self.delb_arcmin)+'\n')
        fobj.write('## Area (arcmin^2): {:12.6f}'.format(self.delb_arcmin**2)+'\n')
        fobj.write('## Baryonic Mass Lower Limit (Msun) : {:10.5e}'.format(self.mass_limit)+'\n')
        fobj.write('## Assumed Cosmology: '+WMAP7.__str__()+'\n')
        fobj.write('## Creator:  Teddy Pena (STScI) \n')
        fobj.write('## Catalog & Data Release Reference:  Nelson et al. (2019) \n')
        fobj.write('## Catalog & Data Release URL: tng-project.org/data \n')
        fobj.write('## Column 01: Snapshot number \n')
        fobj.write('## Column 02: Subhalo Index \n')
        fobj.write('## Column 03: RA (degrees) \n')
        fobj.write('## Column 04: DEC (degrees) \n')
        fobj.write('## Column 05: RA (proper kpc at true z) \n')
        fobj.write('## Column 06: DEC (proper kpc at true z) \n')
        fobj.write('## Column 07: RA (proper kpc at inferred z) \n')
        fobj.write('## Column 08: DEC (proper kpc at inferred z) \n')
        fobj.write('## Column 09: True cosmological redshift \n')
        fobj.write('## Column 10: Inferred redshift (includes peculiar v) \n')
        fobj.write('## Column 11: Peculiar redshift; Peculiar Velocity / Speed of Light \n')
        fobj.write('## Column 12: True scale at cosmological z, in kpc/arcsec \n')
        fobj.write('## Column 13: [Mpc] Comoving X in Observer Coordinates \n')
        fobj.write('## Column 14: [Mpc] Comoving Y in Observer Coordinates \n')
        fobj.write('## Column 15: [Mpc] Comoving Z in Observer Coordinates \n')
        fobj.write('## Column 16: [Mpc] True Angular Diameter Distance to observer \n')
        fobj.write('## Column 17: [Mpc] Inferred Angular Diameter Distance to observer \n')
        fobj.write('## Column 18: Snapshot redshift \n')
        fobj.write('## Column 19: Geometrically appropriate redshift at center of this cylinder \n')
        fobj.write('## Column 20: Lightcone cylinder number \n')
        fobj.write('## Column 21: [Msun] Stellar mass within 2X stellar half mass radius\n')
        fobj.write('## Column 22: [Msun] Total gas mass within 2X stellar half mass radius\n')
        fobj.write('## Column 23: [Msun] Total mass of this subhalo (excludes children subhalos) \n')
        fobj.write('## Column 24: [Msun] Total BH mass within 2X stellar half mass radius\n')
        fobj.write('## Column 25: [Msun] Total baryon mass within 2X stellar half mass radius\n')
        fobj.write('## Column 26: [Msun/year] SFR within 2X stellar half mass radius\n')
        fobj.write('## Column 27: [(10^10 Msun/h) / (0.978 Gyr/h)] Total BH accretion rate within subhalo\n')
        fobj.write('## Column 28: [Mpc] Camera X in Observer Coordinates (Proper X at z; a transverse coordinate) \n')
        fobj.write('## Column 29: [Mpc] Camera Y in Observer Coordinates (Proper Y at z; a transverse coordinate)\n')
        fobj.write('## Column 30: [Mpc] Camera Z in Observer Coordinates (Proper Z at z; should be almost exactly Column 16)\n')
        fobj.write('## Column 31: [AB Mag] Intrinsic stellar g absolute magnitude (BC03) \n')
        fobj.write('## Column 32: [AB Mag] Intrinsic stellar r absolute magnitude (BC03) \n')
        fobj.write('## Column 33: [AB Mag] Intrinsic stellar i absolute magnitude (BC03) \n')
        fobj.write('## Column 34: [AB Mag] Intrinsic stellar z absolute magnitude (BC03) \n')
        fobj.write('## Column 35: [km/s] Galaxy motion in transverse Camera X direction \n')
        fobj.write('## Column 36: [km/s] Galaxy motion in transverse Camera Y direction \n')
        fobj.write('## Column 37: [km/s] Galaxy motion in line-of-sight Camera Z direction ; the Peculiar Velocity \n')
        fobj.write('## Column 38: [km/s] Cosmological expansion velocity at true z (Column 10 measures Column 37+38)\n')
        fobj.write('## Column 39: [AB Mag] Apparent total rest-frame g-band magnitude (BC03) \n')
        
        
        for i,cyl in enumerate(self.cylinder_number):

            cmd_thiscyl = ( (self.v_Egress_x_cmh[i]/ilh - self.v_Ingress_x_cmh[i]/ilh)**2 + (self.v_Egress_y_cmh[i]/ilh - self.v_Ingress_y_cmh[i]/ilh)**2 + (self.v_Egress_z_cmh[i]/ilh - self.v_Ingress_z_cmh[i]/ilh)**2 )**0.5
            cmd_begin = cmd_total
            cmd_end = cmd_begin + cmd_thiscyl
            cmd_total = cmd_end

            cz=self.center_redshift[i]


            #world coordinates of ingress point
            cmx_begin = 1.0*cmx
            cmy_begin = 1.0*cmy
            cmz_begin = 1.0*cmz

            #world coordinates of egress points
            cmx = cmx_begin + (self.v_Egress_x_cmh[i]/ilh - self.v_Ingress_x_cmh[i]/ilh)
            cmy = cmy_begin + (self.v_Egress_y_cmh[i]/ilh - self.v_Ingress_y_cmh[i]/ilh)
            cmz = cmz_begin + (self.v_Egress_z_cmh[i]/ilh - self.v_Ingress_z_cmh[i]/ilh)

            if i > 1000:
                continue

            if cz < minz:
                continue
            if cz > maxz:
                continue


            testf = 'test_'+str(cyl)+'.pdf'
            f1 = plt.figure(figsize=(10.5,10.5), dpi=150)
            plt.subplots_adjust(left=0.11, right=0.98, bottom=0.08, top=0.99,wspace=0.25,hspace=0.25)
            skip = 500

            #determine snapshot of interest
            print("Processing Cylinder: ", cyl, i, self.snapshot_redshift[i])
            snapnum = self.snapshot_string[i]

            # Not relevant for us because we're doing tng simulations
            #old corrupt snaps for illustris-1
            #if snapnum==53:
            #    snapnum=52
            #if snapnum==55:
            #    snapnum=54

            print("    Snapshot Number: ", snapnum)
            
            # Load the data
            fields=['SubhaloMass','SubhaloMassInMaxRad','SubhaloMassInRadType','SubhaloMassInMaxRadType','SubhaloPos','SubhaloSFR','SubhaloSFRinRad','SubhaloVel','SubhaloBHMass','SubhaloBHMdot','SubhaloStellarPhotometrics','SubhaloWindMass']
            subhalos = ilpy.groupcat.loadSubhalos(self.base_dir,snapnum,fields=fields)
            print("    Loaded subhalos: ", subhalos['count'], subhalos['SubhaloMassInRadType'].shape)


            # Clean the loaded subhalo dictionaries
            mstar_msun = subhalos['SubhaloMassInRadType'][:,4]*(1.0e10)/ilh
            mgas_msun = subhalos['SubhaloMassInRadType'][:,0]*(1.0e10)/ilh #includes wind mass
            mbh_msun = subhalos['SubhaloMassInRadType'][:,5]*(1.0e10)/ilh

            baryonmass_msun = mstar_msun + mgas_msun + mbh_msun #within 2x stellar half mass radius... best?

            mhalo_msun = subhalos['SubhaloMass']*(1.0e10)/ilh

            sfr = subhalos['SubhaloSFR']*1.0

            gmag_ABabs=subhalos['SubhaloStellarPhotometrics'][:,4]*1.0
            distmod=illcos.distmod(cz).value
            gmag=gmag_ABabs+distmod




            if self.mag_limit is None:
                mi = np.where(np.logical_and(baryonmass_msun > self.mass_limit, sfr >self.sfr_limit))[0]
            else:
                mi = np.where(np.logical_and(gmag < self.mag_limit,baryonmass_msun > 0.0))[0]


            if mi.shape[0]==0:
                cylinder_obj = None
                self.cylinder_object_list.append(cylinder_obj)
                continue
                f1.close()

            print("    Selected number: ", mi.shape)
            print("    Mstar statistics: ", np.min(mstar_msun[mi]), np.max(mstar_msun[mi]), np.median(mstar_msun[mi]))
            print("    Mgas  statistics: ", np.min(mgas_msun[mi]), np.max(mgas_msun[mi]), np.median(mgas_msun[mi]))
            print("    Mag  statistics : ", np.min(gmag[mi]), np.max(gmag[mi]), np.median(gmag[mi]))
            
            for key in subhalos.keys():
                if key == 'count':
                    continue
                
                filtered_data = subhalos[key][mi]
                
                subhalos[key] = filtered_data
            
            # Now, periodicize
            subhalos = self.periodicize(subhalos,self.L_comovingh*1000.0)



            xpos = subhalos['SubhaloPos'][:,0] #in cKpc/h of max bound part
            ypos = subhalos['SubhaloPos'][:,1]
            zpos = subhalos['SubhaloPos'][:,2]


            #project geometry

            #campos
            #in phys kpc, offset values from lightcone file!
            xoff = self.v_Offset_x_kpc[i]
            yoff = self.v_Offset_y_kpc[i]
            zoff = self.v_Offset_z_kpc[i]

            #the position here I think doesn't matter???
            camera = tc.Camera([0,0,0],[self.camdir_x,self.camdir_y,self.camdir_z],[self.camup_x,self.camup_y,self.camup_z])

            #galaxy world position
            #convert to phys kpc following Renato's lead in translate_coordinates.py
            #note there's an extra translation in the sunrise calcs, so we can discard that here

            #box coordinates relative to ingress coordinate
            boxX = (xpos/ilh) - self.v_Ingress_x_cmh[i]/ilh
            boxY = (ypos/ilh) - self.v_Ingress_y_cmh[i]/ilh
            boxZ = (zpos/ilh) - self.v_Ingress_z_cmh[i]/ilh

            axi = f1.add_subplot(2,2,1)
            axi.set_ylabel('boxI X',size=7,labelpad=1)
            axi.set_xlabel('boxI Z',size=7,labelpad=1)
            axi.tick_params(axis='both',which='major',labelsize=7)
            axi.plot(boxZ[::skip],boxX[::skip],'ok')

            #add box coordinate to world coordinate of ingress point
            worldX = boxX+cmx_begin
            worldY = boxY+cmy_begin
            worldZ = boxZ+cmz_begin

            axi = f1.add_subplot(2,2,2)
            axi.set_ylabel('world X',size=7,labelpad=1)
            axi.set_xlabel('world Z',size=7,labelpad=1)
            axi.tick_params(axis='both',which='major',labelsize=7)
            axi.plot(worldZ[::skip],worldX[::skip],'ok')
            axi.plot([np.min(worldZ),np.max(worldZ)],[cmx_begin,cmx_begin],color='red')



            velX = subhalos['SubhaloVel'][:,0]
            velY = subhalos['SubhaloVel'][:,1]
            velZ = subhalos['SubhaloVel'][:,2]

            #galaxy cam position, in comoving kpc
            galaxy_camera_posx,galaxy_camera_posy,galaxy_camera_posz = camera.cameraCoordinates_vector(worldX,worldY,worldZ)
            galaxy_camera_velx,galaxy_camera_vely,galaxy_camera_velz = camera.cameraCoordinates_vector(velX,velY,velZ)

            axi = f1.add_subplot(2,2,3)
            axi.set_ylabel('cam X',size=7,labelpad=1)
            axi.set_xlabel('cam Z',size=7,labelpad=1)
            axi.tick_params(axis='both',which='major',labelsize=7)
            axi.plot(galaxy_camera_posz[::skip],galaxy_camera_posx[::skip],'ok')


            #galaxy projection using spherical coords
            y1 = np.arctan2(galaxy_camera_posx,galaxy_camera_posz)/(0.5*(self.delb_arcmin/60.0)*(np.pi/180.0))
            y2 = np.arctan2(galaxy_camera_posy,galaxy_camera_posz)/(0.5*(self.delb_arcmin/60.0)*(np.pi/180.0))
            #range = [-1,1] = FOV = self.norm_degrees



            axi = f1.add_subplot(2,2,4)
            axi.set_ylabel('cam Y1',size=7,labelpad=1)
            axi.set_xlabel('cam Y2',size=7,labelpad=1)
            axi.set_xlim(-3,3)
            axi.set_ylim(-3,3)
            axi.tick_params(axis='both',which='major',labelsize=7)
            axi.plot(y1,y2,'ok',markersize=0.5,mew=0.0)
            axi.plot([-1,-1],[-1,1],color='red')
            axi.plot([-1,1],[-1,-1],color='red')
            axi.plot([1,1],[-1,1],color='red')
            axi.plot([-1,1],[1,1],color='red')


            #all values correspond to mi vector
            #cull by RA, DEC, and segment length
            ci = np.where(np.logical_and(np.logical_and(np.logical_and(np.abs(y1) <= 1.0, np.abs(y2) <= 1.0),galaxy_camera_posz <= cmd_end),galaxy_camera_posz > cmd_begin))[0]
            print("    Selected N galaxies in FOV: ", ci.shape)
            axi.plot(y1[ci],y2[ci],'or',markersize=0.7,mew=0.0)



            RA_deg = y1[ci]*self.norm_degrees/2.0
            DEC_deg = y2[ci]*self.norm_degrees/2.0

            #save interesting quantities
            if ci.shape[0] > 0:
                print(cyl, cmd_begin, np.min(galaxy_camera_posz[ci]))
                print(cyl, cmd_end, np.max(galaxy_camera_posz[ci]))

                cylinder_obj = cylinder_catalog(snapnum,subhalos,ci,RA_deg,DEC_deg, self.snapshot_redshift[i],
                                                galaxy_camera_posx,galaxy_camera_posy,galaxy_camera_posz,self.center_redshift[i],
                                                galaxy_camera_velx,galaxy_camera_vely,galaxy_camera_velz,cyl,gmag)
            else:
                cylinder_obj = None

            self.cylinder_object_list.append(cylinder_obj)

            #f1.savefig(testf)
            plt.close(f1)
            
            # Here we write to the file. Note that we're still in the for loop
            # cycling through the snapshot number.
            for i,shi in enumerate(self.subhalo_index):

                thisline = '{:8d}{:12d}  {:12.6f}  {:12.6f}  {:10.2f}  {:10.2f}  {:10.2f}  {:10.2f}  '\
                           '{:12.8f}  {:12.8f}  {:12.4e}  {:8.4f}  '\
                           '{:10.4f}  {:10.4f}  {:16.4f}  {:16.4f}  {:16.4f}  {:12.8f}  {:12.8f}  {:8d}'\
                           '{:12.4e}  {:12.4e}  {:12.4e}  {:12.4e}  {:12.4e}  {:16.4f}  {:10.4e}'\
                           '  {:10.4f}  {:10.4f}  {:16.4f}  {:8.2f}  {:8.2f}  {:8.2f}  {:8.2f}    {:8.2f}  {:8.2f}  {:8.2f}  {:12.4e}  {:8.2f}'\
                           '\n'.format(self.snapshot_number[i],shi,self.RA_deg[i],self.DEC_deg[i],self.RA_kpc[i],self.DEC_kpc[i],self.observed_RA_kpc[i],self.observed_DEC_kpc[i],
                                       self.cosmological_redshift[i],self.galaxy_observed_z[i],self.galaxy_peculiar_z[i],self.kpc_per_arcsec[i],
                                       self.galaxy_comoving_x_mpc[i],self.galaxy_comoving_y_mpc[i],self.galaxy_comoving_z_mpc[i],self.angdiam_mpc[i],
                                       self.observed_angdiam_mpc[i],self.snapz[i],self.center_z[i],self.cylinder_number[i],
                                       self.mstar_msun[i],self.mgas_msun[i],self.mhalo_msun[i],self.mbh_msun[i],self.baryonmass_msun[i],self.sfr[i],self.bhmdot[i],
                                       self.galaxy_camera_posx[i],self.galaxy_camera_posy[i],self.galaxy_camera_posz[i],
                                       self.gmag[i],self.rmag[i],self.imag[i],self.zmag[i],
                                       self.galaxy_camera_velx[i],self.galaxy_camera_vely[i],self.galaxy_camera_velz[i],self.hubble_velocity[i],self.gmag_apparent[i])
            fobj.write(thisline)
            
        
        fobj.close()
        return