35 """Produce a new frame number each time"""
41 """Options for measuring fringes on an exposure"""
42 badMaskPlanes = ListField(dtype=str, default=[
"SAT"], doc=
"Ignore pixels with these masks")
43 stat = Field(dtype=int, default=afwMath.MEDIAN, doc=
"Statistic to use")
44 clip = Field(dtype=float, default=3.0, doc=
"Sigma clip threshold")
45 iterations = Field(dtype=int, default=3, doc=
"Number of fitting iterations")
49 """Fringe subtraction options"""
50 filters = ListField(dtype=str, default=[], doc=
"Only fringe-subtract these filters")
51 num = Field(dtype=int, default=30000, doc=
"Number of fringe measurements")
52 small = Field(dtype=int, default=3, doc=
"Half-size of small (fringe) measurements (pixels)")
53 large = Field(dtype=int, default=30, doc=
"Half-size of large (background) measurements (pixels)")
54 iterations = Field(dtype=int, default=20, doc=
"Number of fitting iterations")
55 clip = Field(dtype=float, default=3.0, doc=
"Sigma clip threshold")
56 stats = ConfigField(dtype=FringeStatisticsConfig, doc=
"Statistics for measuring fringes")
57 pedestal = Field(dtype=bool, default=
False, doc=
"Remove fringe pedestal?")
60 """Task to remove fringes from a science exposure
62 We measure fringe amplitudes at random positions on the science exposure
63 and at the same positions on the (potentially multiple) fringe frames
64 and solve for the scales simultaneously.
66 ConfigClass = FringeConfig
69 def run(self, exposure, dataRef, assembler=None):
70 """Remove fringes from the provided science exposure
72 Fringes are only subtracted if the science exposure has a filter
73 listed in the configuration.
75 @param exposure Science exposure from which to remove fringes
76 @param dataRef Data reference for the science exposure
77 @param assembler An instance of AssembleCcdTask (for assembling fringe frames)
84 fringes = self.
readFringes(dataRef, assembler=assembler)
85 expFringes = self.
measureExposure(exposure, fringes.positions, title=
"Science")
86 solution = self.
solve(expFringes, fringes.fluxes)
87 self.
subtract(exposure, fringes.fringes, solution)
89 ds9.mtv(exposure, title=
"Fringe subtracted", frame=
getFrame())
92 """Check whether we should fringe-subtract the science exposure"""
93 return exposure.getFilter().getName()
in self.config.filters
96 """Read the fringe frame(s) and measure fringe amplitudes.
98 The current implementation assumes only a single fringe frame and
99 will have to be updated to support multi-mode fringe subtraction.
101 This implementation could be optimised by persisting the fringe
102 positions and fluxes.
104 @param dataRef Data reference for the science exposure
105 @param assembler An instance of AssembleCcdTask (for assembling fringe frames)
106 @return Struct(fringes: list of fringe frames;
107 fluxes: fringe amplitues;
108 positions: array of (x,y) for fringe amplitude measurements)
110 fringe = dataRef.get(
"fringe")
111 if assembler
is not None:
112 fringe = assembler.assembleCcd(fringe)
114 if self.config.pedestal:
116 stats.setNumSigmaClip(self.config.stats.clip)
117 stats.setNumIter(self.config.stats.iterations)
118 mi = fringe.getMaskedImage()
120 self.log.info(
"Removing fringe pedestal: %f" % pedestal)
126 return Struct(fringes=[fringe],
127 fluxes=fluxes.reshape([len(positions), 1]),
132 """Generate a random distribution of positions for measuring fringe amplitudes"""
133 start = self.config.large
134 num = self.config.num
135 width = exposure.getWidth() - self.config.large
136 height = exposure.getHeight() - self.config.large
137 return numpy.array([numpy.random.randint(start, width, size=num),
138 numpy.random.randint(start, height, size=num)]).swapaxes(0, 1)
142 """Measure fringe amplitudes for an exposure
144 The fringe amplitudes are measured as the statistic within a square
145 aperture. The statistic within a larger aperture are subtracted so
146 as to remove the background.
148 @param exposure Exposure to measure
149 @param positions Array of (x,y) for fringe measurement
150 @param title Title for display
151 @return Array of fringe measurements
154 stats.setNumSigmaClip(self.config.stats.clip)
155 stats.setNumIter(self.config.stats.iterations)
156 stats.setAndMask(exposure.getMaskedImage().getMask().getPlaneBitMask(self.config.stats.badMaskPlanes))
158 num = self.config.num
159 fringes = numpy.ndarray(num)
163 small =
measure(exposure.getMaskedImage(), x, y, self.config.small, self.config.stats.stat, stats)
164 large =
measure(exposure.getMaskedImage(), x, y, self.config.large, self.config.stats.stat, stats)
165 fringes[i] = small - large
171 ds9.mtv(exposure, frame=frame, title=title)
173 with ds9.Buffering():
174 for x,y
in positions:
175 corners = numpy.array([[-1, -1], [ 1, -1], [ 1, 1], [-1, 1], [-1, -1]]) + [[x, y]]
176 ds9.line(corners * self.config.small, frame=frame, ctype=
"green")
177 ds9.line(corners * self.config.large, frame=frame, ctype=
"blue")
183 """Solve (with iterative clipping) for the scale factors
185 @param science Array of science exposure fringe amplitudes
186 @param fringes Array of arrays of fringe frame fringe amplitudes
187 @return Array of scale factors for the fringe frames
192 origNum = len(science)
194 good = numpy.where(numpy.logical_and(numpy.isfinite(science), numpy.any(numpy.isfinite(fringes), 1)))
195 science = science[good]
196 fringes = fringes[good]
197 oldNum = len(science)
199 for i
in range(self.config.iterations):
200 solution = self.
_solve(science, fringes)
201 resid = science - numpy.sum(solution * fringes, 1)
203 good = numpy.logical_not(abs(resid) > self.config.clip * rms)
204 self.log.logdebug(
"Iteration %d: RMS=%f numGood=%d" % (i, rms, good.sum()))
205 self.log.logdebug(
"Solution %d: %s" % (i, solution))
209 import matplotlib.pyplot
as plot
210 for j
in range(fringes.shape[1]):
214 fig.canvas._tkcanvas._root().lift()
217 ax = fig.add_axes((0.1, 0.1, 0.8, 0.8))
218 adjust = science.copy()
219 others = set(range(fringes.shape[1]))
222 adjust -= solution[k] * fringes[:,k]
223 ax.plot(fringes[:,j], adjust,
'r.')
224 xmin = fringes[:,j].
min()
225 xmax = fringes[:,j].
max()
226 ymin = solution[j] * xmin
227 ymax = solution[j] * xmax
228 ax.plot([xmin, xmax], [ymin, ymax],
'b-')
229 ax.set_title(
"Fringe %d: %f" % (j, solution[j]))
230 ax.set_xlabel(
"Fringe amplitude")
231 ax.set_ylabel(
"Science amplitude")
232 ax.set_autoscale_on(
False)
233 ax.set_xbound(lower=xmin, upper=xmax)
234 ax.set_ybound(lower=ymin, upper=ymax)
237 ans = raw_input(
"Enter or c to continue [chp]").lower()
238 if ans
in (
"",
"c",):
241 import pdb; pdb.set_trace()
243 print "h[elp] c[ontinue] p[db]"
249 good = numpy.where(good)
250 science = science[good]
251 fringes = fringes[good]
254 solution = self.
_solve(science, fringes)
255 self.log.info(
"Fringe solution: %s RMS: %f Good: %d/%d" % (solution, rms, len(science), origNum))
259 """Solve for the scale factors
261 @param science Array of science exposure fringe amplitudes
262 @param fringes Array of arrays of fringe frame fringe amplitudes
263 @return Array of scale factors for the fringe frames
265 return afwMath.LeastSquares.fromDesignMatrix(fringes, science,
266 afwMath.LeastSquares.DIRECT_SVD).getSolution()
269 """Subtract the fringes
271 @param science Science exposure
272 @param fringes List of fringe frames
273 @param solution Array of scale factors for the fringe frames
275 for s, f
in zip(solution, fringes):
276 science.getMaskedImage().scaledMinus(s, f.getMaskedImage())
279 def measure(mi, x, y, size, statistic, stats):
280 """Measure a statistic within an aperture
282 @param mi MaskedImage to measure
283 @param x, y Center for aperture
284 @param size Size of aperture
285 @param statistic Statistic to measure
286 @param stats StatisticsControl object
287 @return Value of statistic within aperture
290 subImage = mi.Factory(mi, bbox, afwImage.LOCAL)
294 """Calculate a robust standard deviation of an array of values
296 @param vector Array of values
297 @return Standard deviation
300 vector = vector.copy()
302 return 0.74 * (vector[int(0.75 * num)] - vector[int(0.25 * num)])
An integer coordinate rectangle.
Pass parameters to a Statistics objectA class to pass parameters which control how the stats are calc...
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.