From 96389ccdc8355aad55450de55839025643d08d0e Mon Sep 17 00:00:00 2001 From: Pierros Papadeas Date: Sun, 6 May 2018 17:45:51 +0300 Subject: [PATCH] Restructure repository and pep8 changes Also all executables now use argparse for arguments and helpers. --- acquire.py | 2 +- astrometry.py | 177 ------------------------------- capture_1.sh | 13 --- capture_2.sh | 13 --- extract_tracks.py | 37 +++++-- imgstat.py | 62 ++++++++--- process.py | 65 +++++++++--- requirements.txt | 7 ++ stars.py | 47 --------- stvid/__init__.py | 0 stvid/astrometry.py | 208 +++++++++++++++++++++++++++++++++++++ stvid/stars.py | 50 +++++++++ stio.py => stvid/stio.py | 0 utils.py => stvid/utils.py | 0 14 files changed, 389 insertions(+), 292 deletions(-) delete mode 100644 astrometry.py delete mode 100755 capture_1.sh delete mode 100755 capture_2.sh mode change 100644 => 100755 extract_tracks.py mode change 100644 => 100755 imgstat.py mode change 100644 => 100755 process.py delete mode 100644 stars.py create mode 100644 stvid/__init__.py create mode 100644 stvid/astrometry.py create mode 100644 stvid/stars.py rename stio.py => stvid/stio.py (100%) rename utils.py => stvid/utils.py (100%) diff --git a/acquire.py b/acquire.py index 7d82817..e497e4d 100644 --- a/acquire.py +++ b/acquire.py @@ -11,7 +11,7 @@ from astropy.coordinates import EarthLocation from astropy.time import Time from astropy.io import fits import astropy.units as u -from utils import get_sunset_and_sunrise +from stvid.utils import get_sunset_and_sunrise import logging import configparser import argparse diff --git a/astrometry.py b/astrometry.py deleted file mode 100644 index 37dcd37..0000000 --- a/astrometry.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function -import os -import numpy as np -import astropy.units as u -from astropy.io import fits -from astropy import wcs -from astropy.coordinates import SkyCoord,FK5,ICRS -from astropy.time import Time -from scipy import optimize - -# Class for the Tycho 2 catalog -class tycho2_catalog: - """Tycho2 catalog""" - - def __init__(self,maxmag=9.0): - hdu=fits.open(os.path.join(os.getenv("ST_DATADIR"),"data/tyc2.fits")) - - ra=hdu[1].data.field('RA')*u.deg - dec=hdu[1].data.field('DEC')*u.deg - mag=hdu[1].data.field('MAG_VT') - - c=mag4): - ast_catalog=tycho2_catalog(10.0) - - # Match catalogs - nmatch=match_catalogs(ast_catalog,pix_catalog,w,10.0) - - # Fit transformation - if nmatch>4: - w,rmsx,rmsy,rms=fit_wcs(w,pix_catalog) - - # Add wcs - add_wcs(fname,w,rmsx,rmsy) - - return w,rmsx,rmsy diff --git a/capture_1.sh b/capture_1.sh deleted file mode 100755 index 14a8b96..0000000 --- a/capture_1.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# Sleep -sleep 10 - -# Does /dev/video1 exist -ls -l /dev/video1 >/tmp/camera_1.log 2>&1 - -# Run mplayer for 10 frames to setup easycap -mplayer -frames 10 tv:// -tv device=/dev/video1 >/tmp/camera_1.log 2>&1 - -# Start script -python ./acquire.py 1 >/tmp/camera_1.log 2>&1 & diff --git a/capture_2.sh b/capture_2.sh deleted file mode 100755 index fd86a4a..0000000 --- a/capture_2.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# Sleep -sleep 10 - -# Does /dev/video2 exist -ls -l /dev/video2 >/tmp/camera_2.log 2>&1 - -# Run mplayer for 10 frames to setup easycap -mplayer -frames 10 tv:// -tv device=/dev/video2 >/tmp/camera_2.log 2>&1 - -# Start script -python ./acquire.py 2 >/tmp/camera_2.log 2>&1 & diff --git a/extract_tracks.py b/extract_tracks.py old mode 100644 new mode 100755 index 35b210c..c2b5be9 --- a/extract_tracks.py +++ b/extract_tracks.py @@ -1,13 +1,14 @@ #!/usr/bin/env python -import time import os import glob import shutil -from stio import fourframe, satid, observation +from stvid.stio import fourframe, satid, observation import numpy as np import ppgplot as ppg from scipy import optimize, ndimage +import configparser +import argparse # Gaussian model @@ -454,15 +455,35 @@ if __name__ == '__main__': # Minimum track points ntrkmin = 10 + # Read commandline options + conf_parser = argparse.ArgumentParser(description='Extract satellite' + + ' tracks from frames.') + conf_parser.add_argument("-c", "--conf_file", + help="Specify configuration file. If no file" + + " is specified 'configuration.ini' is used.", + metavar="FILE") + conf_parser.add_argument("-d", "--directory", + help="Specify directory of observations. If no" + + " directory is specified parent will be used.", + metavar='DIR', dest='file_dir', default=".") + + args = conf_parser.parse_args() + + # Process commandline options and parse configuration + cfg = configparser.ConfigParser(inline_comment_prefixes=('#', ';')) + if args.conf_file: + cfg.read([args.conf_file]) + else: + cfg.read('configuration.ini') + # Create output dirs - path = os.getenv("ST_OBSDIR")+"/"+time.strftime("%Y%m%d/%H%M%S", - time.gmtime()) - os.makedirs(path+"/classfd") - os.makedirs(path+"/catalog") - os.makedirs(path+"/unid") + path = args.file_dir + os.makedirs(path+"classfd") + os.makedirs(path+"catalog") + os.makedirs(path+"unid") # Get files - files = sorted(glob.glob("2*.fits")) + files = sorted(glob.glob(path+"2*.fits")) # Process files for file in files: diff --git a/imgstat.py b/imgstat.py old mode 100644 new mode 100755 index cd67ae9..4e6e644 --- a/imgstat.py +++ b/imgstat.py @@ -4,38 +4,66 @@ import numpy as np from astropy.io import ascii import matplotlib.pyplot as plt import astropy.units as u -from astropy.coordinates import SkyCoord,FK5,AltAz,EarthLocation +from astropy.coordinates import SkyCoord, AltAz, EarthLocation from astropy.time import Time +import configparser +import argparse -table=ascii.read("imgstat.csv",format="csv") +# Read commandline options +conf_parser = argparse.ArgumentParser(description='Plot image statistics') +conf_parser.add_argument("-c", "--conf_file", + help="Specify configuration file. If no file" + + " is specified 'configuration.ini' is used.", + metavar="FILE") +conf_parser.add_argument("-i", "--input", + help="Specify file to be processed. If no file" + + " is specified ./imgstat.csv will be used.", + metavar='FILE', default="./imgstat.csv") +conf_parser.add_argument("-o", "--output", + help="Specify output file. Default is 'imgstat.png'", + metavar='FILE', default="./imgstat.png") -t=Time(table['mjd'],format="mjd",scale="utc") -pos=SkyCoord(ra=table['ra'],dec=table['de'],frame="icrs",unit="deg") +args = conf_parser.parse_args() -loc=EarthLocation(lat=52.8344*u.deg,lon=6.3785*u.deg,height=10*u.m) +# Process commandline options and parse configuration +cfg = configparser.ConfigParser(inline_comment_prefixes=('#', ';')) +if args.conf_file: + cfg.read([args.conf_file]) +else: + cfg.read('configuration.ini') -pa=pos.transform_to(AltAz(obstime=t,location=loc)) +table = ascii.read(args.input, format="csv") -mjd0=np.floor(np.min(table['mjd'])) +t = Time(table['mjd'], format="mjd", scale="utc") +pos = SkyCoord(ra=table['ra'], dec=table['de'], frame="icrs", unit="deg") -plt.figure(figsize=(20,10)) +# Set location +loc = EarthLocation(lat=cfg.getfloat('Common', 'observer_lat')*u.deg, + lon=cfg.getfloat('Common', 'observer_lon')*u.deg, + height=cfg.getfloat('Common', 'observer_el')*u.m) + +pa = pos.transform_to(AltAz(obstime=t, location=loc)) + +mjd0 = np.floor(np.min(table['mjd'])) + +plt.figure(figsize=(20, 10)) plt.subplot(411) -plt.plot(table['mjd']-mjd0,table['mean'],label='Brightness') -plt.plot(table['mjd']-mjd0,table['std'],label='Variation') +plt.plot(table['mjd']-mjd0, table['mean'], label='Brightness') +plt.plot(table['mjd']-mjd0, table['std'], label='Variation') plt.ylabel("ADU") plt.legend() plt.subplot(412) -plt.plot(table['mjd']-mjd0,pa.az.degree) +plt.plot(table['mjd']-mjd0, pa.az.degree) plt.ylabel("Azimuth (deg)") plt.subplot(413) -plt.plot(table['mjd']-mjd0,pa.alt.degree) +plt.plot(table['mjd']-mjd0, pa.alt.degree) plt.ylabel("Altitude (deg)") plt.subplot(414) -plt.plot(table['mjd']-mjd0,table['rmsx'],label='RA') -plt.plot(table['mjd']-mjd0,table['rmsy'],label='Dec') -plt.ylim(0,60) +plt.plot(table['mjd']-mjd0, table['rmsx'], label='RA') +plt.plot(table['mjd']-mjd0, table['rmsy'], label='Dec') +plt.ylim(0, 60) plt.ylabel("Residual (arcseconds)") -plt.xlabel("MJD - %.0f"%mjd0) +plt.xlabel("MJD - %.0f" % mjd0) plt.legend() -plt.savefig("imgstat.png") +plt.savefig(args.output) diff --git a/process.py b/process.py old mode 100644 new mode 100755 index c52c7be..cf271db --- a/process.py +++ b/process.py @@ -2,46 +2,79 @@ from __future__ import print_function import glob import numpy as np -from stio import fourframe -from stars import pixel_catalog, generate_star_catalog -from astrometry import calibrate_from_reference +from stvid.stio import fourframe +from stvid.stars import generate_star_catalog +from stvid.astrometry import calibrate_from_reference import astropy.units as u from astropy.utils.exceptions import AstropyWarning -from astropy.coordinates import EarthLocation, AltAz +from astropy.coordinates import EarthLocation import warnings +import configparser +import argparse if __name__ == "__main__": + + # Read commandline options + conf_parser = argparse.ArgumentParser(description='Process captured' + + ' video frames.') + conf_parser.add_argument("-c", "--conf_file", + help="Specify configuration file. If no file" + + " is specified 'configuration.ini' is used.", + metavar="FILE") + conf_parser.add_argument("-d", "--directory", + help="Specify directory of observations. If no" + + " directory is specified parent will be used.", + metavar='DIR', dest='file_dir', default=".") + + args = conf_parser.parse_args() + + # Process commandline options and parse configuration + cfg = configparser.ConfigParser(inline_comment_prefixes=('#', ';')) + if args.conf_file: + cfg.read([args.conf_file]) + else: + cfg.read('configuration.ini') + # Set warnings warnings.filterwarnings("ignore", category=UserWarning, append=True) warnings.simplefilter("ignore", AstropyWarning) # Set location - loc=EarthLocation(lat=52.8344*u.deg,lon=6.3785*u.deg,height=10*u.m) + loc = EarthLocation(lat=cfg.getfloat('Common', 'observer_lat')*u.deg, + lon=cfg.getfloat('Common', 'observer_lon')*u.deg, + height=cfg.getfloat('Common', 'observer_el')*u.m) # Get files - files=sorted(glob.glob("2*.fits")) + files = sorted(glob.glob(args.file_dir+"2*.fits")) # Statistics file - fstat=open("imgstat.csv","w") + fstat = open(args.file_dir+"imgstat.csv", "w") fstat.write("fname,mjd,ra,de,rmsx,rmsy,mean,std,nstars,nused\n") - + # Loop over files for fname in files: # Generate star catalog - pix_catalog=generate_star_catalog(fname) + pix_catalog = generate_star_catalog(fname) # Calibrate astrometry - calibrate_from_reference(fname,"test.fits",pix_catalog) + calibrate_from_reference(fname, args.file_dir+"test.fits", pix_catalog) # Stars available and used - nused=np.sum(pix_catalog.flag==1) - nstars=pix_catalog.nstars + nused = np.sum(pix_catalog.flag == 1) + nstars = pix_catalog.nstars # Get properties - ff=fourframe(fname) - - print("%s,%.8lf,%.6f,%.6f,%.3f,%.3f,%.3f,%.3f,%d,%d"%(ff.fname,ff.mjd,ff.crval[0],ff.crval[1],3600*ff.crres[0],3600*ff.crres[1],np.mean(ff.zavg),np.std(ff.zavg),nstars,nused)) - fstat.write("%s,%.8lf,%.6f,%.6f,%.3f,%.3f,%.3f,%.3f,%d,%d\n"%(ff.fname,ff.mjd,ff.crval[0],ff.crval[1],3600*ff.crres[0],3600*ff.crres[1],np.mean(ff.zavg),np.std(ff.zavg),nstars,nused)) + ff = fourframe(fname) + print(("%s,%.8lf,%.6f,%.6f,%.3f,%.3f," + + "%.3f,%.3f,%d,%d") % (ff.fname, ff.mjd, ff.crval[0], + ff.crval[1], 3600*ff.crres[0], + 3600*ff.crres[1], np.mean(ff.zavg), + np.std(ff.zavg), nstars, nused)) + fstat.write(("%s,%.8lf,%.6f,%.6f,%.3f,%.3f,%.3f," + + "%.3f,%d,%d\n") % (ff.fname, ff.mjd, ff.crval[0], + ff.crval[1], 3600*ff.crres[0], + 3600*ff.crres[1], np.mean(ff.zavg), + np.std(ff.zavg), nstars, nused)) fstat.close() diff --git a/requirements.txt b/requirements.txt index f1afd48..6fbd383 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,10 @@ numpy==1.14.2 opencv-python==3.4.0.12 scipy==1.0.1 git+https://github.com/cbassa/ppgplot.git@master +cycler==0.10.0 +kiwisolver==1.0.1 +matplotlib==2.2.2 +pyparsing==2.2.0 +python-dateutil==2.7.2 +pytz==2018.4 +six==1.11.0 diff --git a/stars.py b/stars.py deleted file mode 100644 index e12b4c1..0000000 --- a/stars.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function -import os -import subprocess -import numpy as np - -class pixel_catalog: - """Pixel catalog""" - - def __init__(self,fname): - d=np.loadtxt(fname) - if len(d.shape)==2: - self.x=d[:,0] - self.y=d[:,1] - self.mag=d[:,2] - self.ra=np.empty_like(self.x) - self.dec=np.empty_like(self.x) - self.imag=np.empty_like(self.x) - self.flag=np.zeros_like(self.x) - self.nstars=len(self.mag) - else: - self.x=None - self.y=None - self.mag=None - self.ra=None - self.dec=None - self.imag=None - self.flag=None - self.nstars=0 - -def generate_star_catalog(fname): - # Skip if file already exists - if not os.path.exists(fname+".cat"): - # Get sextractor location - env=os.getenv("ST_DATADIR") - - # Format command - command="sextractor %s -c %s/sextractor/default.sex"%(fname,env) - - # Run sextractor - output=subprocess.check_output(command,shell=True,stderr=subprocess.STDOUT) - - # Rename file - if os.path.exists("test.cat"): - os.rename("test.cat",fname+".cat") - - return pixel_catalog(fname+".cat") diff --git a/stvid/__init__.py b/stvid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stvid/astrometry.py b/stvid/astrometry.py new file mode 100644 index 0000000..52e982b --- /dev/null +++ b/stvid/astrometry.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python +from __future__ import print_function +import os +import numpy as np +import astropy.units as u +from astropy.io import fits +from astropy import wcs +from astropy.coordinates import SkyCoord, FK5, ICRS +from astropy.time import Time +from scipy import optimize + + +# Class for the Tycho 2 catalog +class tycho2_catalog: + """Tycho2 catalog""" + + def __init__(self, maxmag=9.0): + hdu = fits.open(os.path.join(os.getenv("ST_DATADIR"), + "data/tyc2.fits")) + + ra = hdu[1].data.field('RA')*u.deg + dec = hdu[1].data.field('DEC')*u.deg + mag = hdu[1].data.field('MAG_VT') + + c = mag < maxmag + self.ra = ra[c] + self.dec = dec[c] + self.mag = mag[c] + + +# Estimate the WCS from a reference file +def estimate_wcs_from_reference(ref, fname): + # Read header of reference + hdu = fits.open(ref) + hdu[0].header["NAXIS"] = 2 + w = wcs.WCS(hdu[0].header) + + # Get time and position from reference + tref = Time(hdu[0].header["MJD-OBS"], format="mjd", scale="utc") + pref = SkyCoord(ra=w.wcs.crval[0], + dec=w.wcs.crval[1], + unit="deg", + frame="icrs").transform_to(FK5(equinox=tref)) + + # Read time from target + hdu = fits.open(fname) + t = Time(hdu[0].header["MJD-OBS"], format="mjd", scale="utc") + + # Correct wcs + dra = (t.sidereal_time("mean", "greenwich") + - tref.sidereal_time("mean", "greenwich")) + p = FK5(ra=pref.ra+dra, dec=pref.dec, equinox=t).transform_to(ICRS) + w.wcs.crval = np.array([p.ra.degree, p.dec.degree]) + + return w + + +# Match the astrometry and pixel catalog +def match_catalogs(ast_catalog, pix_catalog, w, rmin): + # Select stars towards pointing center + ra, dec = w.wcs.crval*u.deg + d = np.arccos(np.sin(dec) + * np.sin(ast_catalog.dec) + + np.cos(dec) + * np.cos(ast_catalog.dec) + * np.cos(ra-ast_catalog.ra)) + c = (d < 30.0*u.deg) + ra, dec, mag = ast_catalog.ra[c], ast_catalog.dec[c], ast_catalog.mag[c] + + # Convert RA/Dec to pixels + pix = w.wcs_world2pix(np.stack((ra, dec), axis=-1), 0) + xs, ys = pix[:, 0], pix[:, 1] + + # Loop over stars + nmatch = 0 + for i in range(len(pix_catalog.x)): + dx = xs-pix_catalog.x[i] + dy = ys-pix_catalog.y[i] + r = np.sqrt(dx*dx+dy*dy) + if np.min(r) < rmin: + j = np.argmin(r) + pix_catalog.ra[i] = ra[j].value + pix_catalog.dec[i] = dec[j].value + pix_catalog.imag[i] = mag[j] + pix_catalog.flag[i] = 1 + nmatch += 1 + + return nmatch + + +# Residual function +def residual(a, x, y, z): + return z-(a[0]+a[1]*x+a[2]*y) + + +# Fit transformation +def fit_wcs(w, pix_catalog): + x0, y0 = w.wcs.crpix + ra0, dec0 = w.wcs.crval + dx, dy = pix_catalog.x-x0, pix_catalog.y-y0 + + # Iterate to remove outliers + nstars = np.sum(pix_catalog.flag == 1) + for j in range(10): + w = wcs.WCS(naxis=2) + w.wcs.crpix = np.array([0.0, 0.0]) + w.wcs.cd = np.array([[1.0, 0.0], [0.0, 1.0]]) + w.wcs.ctype = ["RA---TAN", "DEC--TAN"] + w.wcs.set_pv([(2, 1, 45.0)]) + c = pix_catalog.flag == 1 + + # Iterate to move crval to crpix location + for i in range(5): + w.wcs.crval = np.array([ra0, dec0]) + + world = np.stack((pix_catalog.ra, pix_catalog.dec), axis=-1) + pix = w.wcs_world2pix(world, 1) + rx, ry = pix[:, 0], pix[:, 1] + + ax, cov_q, infodict, mesg, ierr = optimize.leastsq(residual, + [0.0, 0.0, 0.0], + args=(dx[c], + dy[c], + rx[c]), + full_output=1) + ay, cov_q, infodict, mesg, ierr = optimize.leastsq(residual, + [0.0, 0.0, 0.0], + args=(dx[c], + dy[c], + ry[c]), + full_output=1) + + ra0, dec0 = w.wcs_pix2world([[ax[0], ay[0]]], 1)[0] + + # Compute residuals + drx = ax[0]+ax[1]*dx+ax[2]*dy-rx + dry = ay[0]+ay[1]*dx+ay[2]*dy-ry + dr = np.sqrt(drx*drx+dry*dry) + rms = np.sqrt(np.sum(dr[c]**2)/len(dr[c])) + + dr[~c] = 1.0 + c = (dr < 2.0*rms) + pix_catalog.flag[~c] = 0 + + # Break if converged + if np.sum(c) == nstars: + break + nstars = np.sum(c) + + # Compute residuals + rmsx = np.sqrt(np.sum(drx[c]**2)/len(drx[c])) + rmsy = np.sqrt(np.sum(dry[c]**2)/len(dry[c])) + + # Store header + w = wcs.WCS(naxis=2) + w.wcs.crpix = np.array([x0, y0]) + w.wcs.crval = np.array([ra0, dec0]) + w.wcs.cd = np.array([[ax[1], ax[2]], [ay[1], ay[2]]]) + w.wcs.ctype = ["RA---TAN", "DEC--TAN"] + w.wcs.set_pv([(2, 1, 45.0)]) + + return w, rmsx, rmsy, rms + + +def add_wcs(fname, w, rmsx, rmsy): + # Read fits + hdu = fits.open(fname) + + whdr = {"CRPIX1": w.wcs.crpix[0], "CRPIX2": w.wcs.crpix[1], + "CRVAL1": w.wcs.crval[0], "CRVAL2": w.wcs.crval[1], + "CD1_1": w.wcs.cd[0, 0], "CD1_2": w.wcs.cd[0, 1], + "CD2_1": w.wcs.cd[1, 0], "CD2_2": w.wcs.cd[1, 1], + "CTYPE1": "RA---TAN", "CTYPE2": "DEC--TAN", "CUNIT1": "DEG", + "CUNIT2": "DEG", "CRRES1": rmsx, "CRRES2": rmsy} + + # Add keywords + hdr = hdu[0].header + for k, v in whdr.items(): + hdr[k] = v + hdu = fits.PrimaryHDU(header=hdr, data=hdu[0].data) + hdu.writeto(fname, overwrite=True, output_verify="ignore") + + return + + +def calibrate_from_reference(fname, ref, pix_catalog): + # Estimated WCS + w = estimate_wcs_from_reference(ref, fname) + + # Default rms values + rmsx = 0.0 + rmsy = 0.0 + + # Read catalogs + if (pix_catalog.nstars > 4): + ast_catalog = tycho2_catalog(10.0) + + # Match catalogs + nmatch = match_catalogs(ast_catalog, pix_catalog, w, 10.0) + + # Fit transformation + if nmatch > 4: + w, rmsx, rmsy, rms = fit_wcs(w, pix_catalog) + + # Add wcs + add_wcs(fname, w, rmsx, rmsy) + + return w, rmsx, rmsy diff --git a/stvid/stars.py b/stvid/stars.py new file mode 100644 index 0000000..5720f87 --- /dev/null +++ b/stvid/stars.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +from __future__ import print_function +import os +import subprocess +import numpy as np + + +class pixel_catalog: + """Pixel catalog""" + + def __init__(self, fname): + d = np.loadtxt(fname) + if len(d.shape) == 2: + self.x = d[:, 0] + self.y = d[:, 1] + self.mag = d[:, 2] + self.ra = np.empty_like(self.x) + self.dec = np.empty_like(self.x) + self.imag = np.empty_like(self.x) + self.flag = np.zeros_like(self.x) + self.nstars = len(self.mag) + else: + self.x = None + self.y = None + self.mag = None + self.ra = None + self.dec = None + self.imag = None + self.flag = None + self.nstars = 0 + + +def generate_star_catalog(fname): + # Skip if file already exists + if not os.path.exists(fname+".cat"): + # Get sextractor location + env = os.getenv("ST_DATADIR") + + # Format command + command = "sextractor %s -c %s/sextractor/default.sex" % (fname, env) + + # Run sextractor + output = subprocess.check_output(command, shell=True, + stderr=subprocess.STDOUT) + + # Rename file + if os.path.exists("test.cat"): + os.rename("test.cat", fname+".cat") + + return pixel_catalog(fname+".cat") diff --git a/stio.py b/stvid/stio.py similarity index 100% rename from stio.py rename to stvid/stio.py diff --git a/utils.py b/stvid/utils.py similarity index 100% rename from utils.py rename to stvid/utils.py