-
Notifications
You must be signed in to change notification settings - Fork 7
/
Stamper.py
170 lines (126 loc) · 6.07 KB
/
Stamper.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import os
import Catalogs
import numpy as np
import astropy
import logging
from settings import log_file_handler
logger = logging.getLogger(__name__)
logger.addHandler(log_file_handler)
def dndmag(m):
# a rough fit to a random (small) field
f = np.exp(-3.293 + 0.697 * m)
return f
class Stamper(object):
def __init__(self, specifier, ccd):
self.specifier = specifier
self.ccd = ccd
self.camera = self.ccd.camera
# create stamps randomly, just with a given number per CCD
if specifier is None:
self.fromNothing()
elif type(specifier) == int:
self.fromRandom(nstamps=specifier)
elif type(specifier) == str:
self.fromFile(filename=specifier)
def xy(self):
# assign the cartrographer's CCD to this one
self.camera.cartographer.ccd = self.ccd
# create coordinate object for the stars
stars = self.camera.cartographer.point(self.ra, self.dec, 'celestial')
# return the CCD xy coordinates
return stars.ccdxy.tuple
def write(self):
# create an astropy table
t = astropy.table.Table([self.ra, self.dec, self.radii], names=('ra', 'dec', 'radius'))
# write it out
f = os.path.join(
self.ccd.directory, 'postagestamptargets_{pos}_{name}.txt'.format(pos=self.ccd.pos_string,
name=self.ccd.name))
t.write(f, format='ascii.fixed_width', delimiter='|', bookend=False)
logger.info('wrote stamp definition catalog to {}'.format(f))
def fromNothing(self):
logger.info('no postage stamps defined')
self.ccd.stampimage = self.ccd.ones()
def fromRandom(self, nstamps, radius=10):
logger.info('randomly populating {} with {} postage stamps'.format(self.ccd.name, nstamps))
# select (randomly) some target stars, seeded by the CCD number
np.random.seed(self.ccd.number)
ras, decs, tmag, temperatures = self.camera.catalog.snapshot(self.camera.bjd,
exptime=self.camera.cadence / 60.0 / 60.0 / 24.0)
# assign the cartrographer's CCD to this one
self.camera.cartographer.ccd = self.ccd
# create coordinate object for the stars
stars = self.camera.cartographer.point(ras, decs, 'celestial')
x, y = stars.ccdxy.tuple
# start with targets with centers on the chip
onccd = (np.round(x) > 0) & \
(np.round(x) < self.ccd.xsize) & \
(np.round(y) > 0) & \
(np.round(y) < self.ccd.ysize)
# weight stars inversely to their abundance, to give roughly uniform distribution of magnitudes
weights = \
(1.0 / dndmag(self.camera.catalog.tmag) * (self.camera.catalog.tmag >= 6) * (
self.camera.catalog.tmag <= 16))[
onccd]
weights /= np.sum(weights)
itargets = np.random.choice(onccd.nonzero()[0],
size=np.minimum(nstamps, np.sum(weights != 0)),
replace=False,
p=weights)
# populate position arrays
self.ra = self.camera.catalog.ra[itargets]
self.dec = self.camera.catalog.dec[itargets]
self.radii = np.ones_like(self.ra) * radius
self.finishFromStars()
def finishFromStars(self):
# convert to ccd coordinates
self.x, self.y = self.xy()
# write out the stamp catalog
self.write()
# populate the stamp image
self.populateStampImage()
def fromFile(self, filename):
logger.info('loading RA and Dec stamp centers from {}'.format(filename))
self.table = astropy.io.ascii.read(filename, names=['ra', 'dec', 'radius'])
self.ra = self.table['ra'].data
self.dec = self.table['dec'].data
self.radii = self.table['radius'].data
self.finishFromStars()
def trimCatalog(self, catalog):
'''trim a catalog to contain only stars that fall on the CCD and in a stamp'''
# assuming stars don't move in and out of postage stamps over time
ras, decs, tmag, temperatures = self.camera.catalog.snapshot(self.camera.bjd,
exptime=self.camera.cadence / 60.0 / 60.0 / 24.0)
# assign the cartrographer's CCD to this one
self.camera.cartographer.ccd = self.ccd
# create coordinate object for the stars
stars = self.camera.cartographer.point(ras, decs, 'celestial')
x, y = stars.ccdxy.integerpixels
onccd = (x >= 0) * (x < self.ccd.xsize) * (y >= 0) * (y < self.ccd.ysize)
ok = np.ones_like(onccd)
ok[onccd] *= self.ccd.stampimage[y[onccd], x[onccd]].astype(np.bool)
return Catalogs.Trimmed(self.camera.catalog, ok)
def populateStampImage(self):
x = self.x
y = self.y
radii = self.radii
# create an empty array
mask = self.ccd.zeros()
# create a (currently fixed size and shape) aperture
diameter = max(radii) * 2 + 1
xgrid, ygrid = np.meshgrid(np.arange(-max(radii), max(radii) + 1), np.arange(-max(radii), max(radii) + 1))
# make a circular aperture
r = np.sqrt(xgrid ** 2 + ygrid ** 2)
# loop over stars
for i in range(len(x)):
if (x[i] >= 0) * (x[i] < self.ccd.xsize) * (y[i] >= 0) * (y[i] < self.ccd.ysize):
thisx = np.minimum(np.maximum(np.round(x[i]).astype(np.int) + xgrid, 0), self.ccd.xsize - 1).astype(
np.int)
thisy = np.minimum(np.maximum(np.round(y[i]).astype(np.int) + ygrid, 0), self.ccd.ysize - 1).astype(
np.int)
aperture = r <= radii[i]
mask[thisy, thisx] += aperture
self.ccd.stampimage = mask
self.ccd.note = 'stampdefinition'
stampfilename = os.path.join(self.ccd.directory, self.ccd.note + '.fits')
self.ccd.writeToFITS(self.ccd.stampimage, stampfilename)