lsst.ip.diffim  16.0-12-g1dc09ba+6
dcrModel.py
Go to the documentation of this file.
1 # This file is part of ip_diffim.
2 #
3 # LSST Data Management System
4 # This product includes software developed by the
5 # LSST Project (http://www.lsst.org/).
6 # See COPYRIGHT file at the top of the source tree.
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <https://www.lsstcorp.org/LegalNotices/>.
21 #
22 
23 import numpy as np
24 from scipy import ndimage
25 from lsst.afw.coord.refraction import differentialRefraction
26 import lsst.afw.geom as afwGeom
27 from lsst.afw.geom import AffineTransform
28 from lsst.afw.geom import makeTransform
29 import lsst.afw.image as afwImage
30 import lsst.afw.math as afwMath
31 from lsst.geom import radians
32 
33 __all__ = ["DcrModel", "applyDcr", "calculateDcr", "calculateImageParallacticAngle"]
34 
35 
36 class DcrModel:
37  """A model of the true sky after correcting chromatic effects.
38 
39  Attributes
40  ----------
41  dcrNumSubfilters : `int`
42  Number of sub-filters used to model chromatic effects within a band.
43  filterInfo : `lsst.afw.image.Filter`
44  The filter definition, set in the current instruments' obs package.
45  modelImages : `list` of `lsst.afw.image.MaskedImage`
46  A list of masked images, each containing the model for one subfilter
47 
48  Parameters
49  ----------
50  modelImages : `list` of `lsst.afw.image.MaskedImage`
51  A list of masked images, each containing the model for one subfilter.
52  filterInfo : `lsst.afw.image.Filter`, optional
53  The filter definition, set in the current instruments' obs package.
54  Required for any calculation of DCR, including making matched templates.
55 
56  Notes
57  -----
58  The ``DcrModel`` contains an estimate of the true sky, at a higher
59  wavelength resolution than the input observations. It can be forward-
60  modeled to produce Differential Chromatic Refraction (DCR) matched
61  templates for a given ``Exposure``, and provides utilities for conditioning
62  the model in ``dcrAssembleCoadd`` to avoid oscillating solutions between
63  iterations of forward modeling or between the subfilters of the model.
64  """
65 
66  def __init__(self, modelImages, filterInfo=None, psf=None):
67  self.dcrNumSubfilters = len(modelImages)
68  self.modelImages = modelImages
69  self._filter = filterInfo
70  self._psf = psf
71 
72  @classmethod
73  def fromImage(cls, maskedImage, dcrNumSubfilters, filterInfo=None, psf=None):
74  """Initialize a DcrModel by dividing a coadd between the subfilters.
75 
76  Parameters
77  ----------
78  maskedImage : `lsst.afw.image.MaskedImage`
79  Input coadded image to divide equally between the subfilters.
80  dcrNumSubfilters : `int`
81  Number of sub-filters used to model chromatic effects within a band.
82  filterInfo : `lsst.afw.image.Filter`, optional
83  The filter definition, set in the current instruments' obs package.
84  Required for any calculation of DCR, including making matched templates.
85  psf : `lsst.afw.detection.Psf`, optional
86  Point spread function (PSF) of the model.
87  Required if the ``DcrModel`` will be persisted.
88 
89  Returns
90  -------
91  dcrModel : `lsst.pipe.tasks.DcrModel`
92  Best fit model of the true sky after correcting chromatic effects.
93  """
94  # NANs will potentially contaminate the entire image,
95  # depending on the shift or convolution type used.
96  model = maskedImage.clone()
97  badPixels = np.isnan(model.image.array) | np.isnan(model.variance.array)
98  model.image.array[badPixels] = 0.
99  model.variance.array[badPixels] = 0.
100  model.image.array /= dcrNumSubfilters
101  # We divide the variance by N and not N**2 because we will assume each
102  # subfilter is independent. That means that the significance of
103  # detected sources will be lower by a factor of sqrt(N) in the
104  # subfilter images, but we will recover it when we combine the
105  # subfilter images to construct matched templates.
106  model.variance.array /= dcrNumSubfilters
107  model.mask.array[badPixels] = model.mask.getPlaneBitMask("NO_DATA")
108  modelImages = [model, ]
109  for subfilter in range(1, dcrNumSubfilters):
110  modelImages.append(model.clone())
111  return cls(modelImages, filterInfo, psf)
112 
113  @classmethod
114  def fromDataRef(cls, dataRef, datasetType="dcrCoadd", numSubfilters=None, **kwargs):
115  """Load an existing DcrModel from a repository.
116 
117  Parameters
118  ----------
119  dataRef : `lsst.daf.persistence.ButlerDataRef`
120  Data reference defining the patch for coaddition and the
121  reference Warp
122  datasetType : `str`, optional
123  Name of the DcrModel in the registry {"dcrCoadd", "dcrCoadd_sub"}
124  numSubfilters : `int`
125  Number of sub-filters used to model chromatic effects within a band.
126  **kwargs
127  Additional keyword arguments to pass to look up the model in the data registry.
128  Common keywords and their types include: ``tract``:`str`, ``patch``:`str`,
129  ``bbox``:`lsst.afw.geom.Box2I`
130 
131  Returns
132  -------
133  dcrModel : `lsst.pipe.tasks.DcrModel`
134  Best fit model of the true sky after correcting chromatic effects.
135  """
136  modelImages = []
137  filterInfo = None
138  psf = None
139  for subfilter in range(numSubfilters):
140  dcrCoadd = dataRef.get(datasetType, subfilter=subfilter,
141  numSubfilters=numSubfilters, **kwargs)
142  if filterInfo is None:
143  filterInfo = dcrCoadd.getFilter()
144  if psf is None:
145  psf = dcrCoadd.getPsf()
146  modelImages.append(dcrCoadd.maskedImage)
147  return cls(modelImages, filterInfo, psf)
148 
149  def __len__(self):
150  """Return the number of subfilters.
151 
152  Returns
153  -------
154  dcrNumSubfilters : `int`
155  The number of DCR subfilters in the model.
156  """
157  return self.dcrNumSubfilters
158 
159  def __getitem__(self, subfilter):
160  """Iterate over the subfilters of the DCR model.
161 
162  Parameters
163  ----------
164  subfilter : `int`
165  Index of the current ``subfilter`` within the full band.
166  Negative indices are allowed, and count in reverse order
167  from the highest ``subfilter``.
168 
169  Returns
170  -------
171  modelImage : `lsst.afw.image.MaskedImage`
172  The DCR model for the given ``subfilter``.
173 
174  Raises
175  ------
176  IndexError
177  If the requested ``subfilter`` is greater or equal to the number
178  of subfilters in the model.
179  """
180  if np.abs(subfilter) >= len(self):
181  raise IndexError("subfilter out of bounds.")
182  return self.modelImages[subfilter]
183 
184  def __setitem__(self, subfilter, maskedImage):
185  """Update the model image for one subfilter.
186 
187  Parameters
188  ----------
189  subfilter : `int`
190  Index of the current subfilter within the full band.
191  maskedImage : `lsst.afw.image.MaskedImage`
192  The DCR model to set for the given ``subfilter``.
193 
194  Raises
195  ------
196  IndexError
197  If the requested ``subfilter`` is greater or equal to the number
198  of subfilters in the model.
199  ValueError
200  If the bounding box of the new image does not match.
201  """
202  if np.abs(subfilter) >= len(self):
203  raise IndexError("subfilter out of bounds.")
204  if maskedImage.getBBox() != self.bbox:
205  raise ValueError("The bounding box of a subfilter must not change.")
206  self.modelImages[subfilter] = maskedImage
207 
208  @property
209  def filter(self):
210  """Return the filter of the model.
211 
212  Returns
213  -------
214  filter : `lsst.afw.image.Filter`
215  The filter definition, set in the current instruments' obs package.
216  """
217  return self._filter
218 
219  @property
220  def psf(self):
221  """Return the psf of the model.
222 
223  Returns
224  -------
225  psf : `lsst.afw.detection.Psf`
226  Point spread function (PSF) of the model.
227  """
228  return self._psf
229 
230  @property
231  def bbox(self):
232  """Return the common bounding box of each subfilter image.
233 
234  Returns
235  -------
236  bbox : `lsst.afw.geom.Box2I`
237  Bounding box of the DCR model.
238  """
239  return self[0].getBBox()
240 
241  @property
242  def mask(self):
243  """Return the common mask of each subfilter image.
244 
245  Returns
246  -------
247  bbox : `lsst.afw.image.Mask`
248  Mask plane of the DCR model.
249  """
250  return self[0].mask
251 
252  def getReferenceImage(self, bbox=None):
253  """Create a simple template from the DCR model.
254 
255  Parameters
256  ----------
257  bbox : `lsst.afw.geom.Box2I`, optional
258  Sub-region of the coadd. Returns the entire image if `None`.
259 
260  Returns
261  -------
262  templateImage : `numpy.ndarray`
263  The template with no chromatic effects applied.
264  """
265  bbox = bbox or self.bbox
266  return np.mean([model[bbox].image.array for model in self], axis=0)
267 
268  def assign(self, dcrSubModel, bbox=None):
269  """Update a sub-region of the ``DcrModel`` with new values.
270 
271  Parameters
272  ----------
273  dcrSubModel : `lsst.pipe.tasks.DcrModel`
274  New model of the true scene after correcting chromatic effects.
275  bbox : `lsst.afw.geom.Box2I`, optional
276  Sub-region of the coadd.
277  Defaults to the bounding box of ``dcrSubModel``.
278 
279  Raises
280  ------
281  ValueError
282  If the new model has a different number of subfilters.
283  """
284  if len(dcrSubModel) != len(self):
285  raise ValueError("The number of DCR subfilters must be the same "
286  "between the old and new models.")
287  bbox = bbox or self.bbox
288  for model, subModel in zip(self, dcrSubModel):
289  model.assign(subModel[bbox], bbox)
290 
291  def buildMatchedTemplate(self, exposure=None, warpCtrl=None,
292  visitInfo=None, bbox=None, wcs=None, mask=None):
293  """Create a DCR-matched template image for an exposure.
294 
295  Parameters
296  ----------
297  exposure : `lsst.afw.image.Exposure`, optional
298  The input exposure to build a matched template for.
299  May be omitted if all of the metadata is supplied separately
300  warpCtrl : `lsst.afw.Math.WarpingControl`, optional
301  Configuration settings for warping an image.
302  If not set, defaults to a lanczos3 warping kernel for the image,
303  and a bilinear kernel for the mask
304  visitInfo : `lsst.afw.image.VisitInfo`, optional
305  Metadata for the exposure. Ignored if ``exposure`` is set.
306  bbox : `lsst.afw.geom.Box2I`, optional
307  Sub-region of the coadd. Ignored if ``exposure`` is set.
308  wcs : `lsst.afw.geom.SkyWcs`, optional
309  Coordinate system definition (wcs) for the exposure.
310  Ignored if ``exposure`` is set.
311  mask : `lsst.afw.image.Mask`, optional
312  reference mask to use for the template image.
313 
314  Returns
315  -------
316  templateImage : `lsst.afw.image.maskedImageF`
317  The DCR-matched template
318 
319  Raises
320  ------
321  ValueError
322  If neither ``exposure`` or all of ``visitInfo``, ``bbox``, and ``wcs`` are set.
323  """
324  if self.filter is None:
325  raise ValueError("'filterInfo' must be set for the DcrModel in order to calculate DCR.")
326  if exposure is not None:
327  visitInfo = exposure.getInfo().getVisitInfo()
328  bbox = exposure.getBBox()
329  wcs = exposure.getInfo().getWcs()
330  elif visitInfo is None or bbox is None or wcs is None:
331  raise ValueError("Either exposure or visitInfo, bbox, and wcs must be set.")
332  if warpCtrl is None:
333  # Turn off the warping cache, since we set the linear interpolation length to the entire subregion
334  # This warper is only used for applying DCR shifts, which are assumed to be uniform across a patch
335  warpCtrl = afwMath.WarpingControl("lanczos3", "bilinear",
336  cacheSize=0, interpLength=max(bbox.getDimensions()))
337 
338  dcrShift = calculateDcr(visitInfo, wcs, self.filter, len(self))
339  templateImage = afwImage.MaskedImageF(bbox)
340  for subfilter, dcr in enumerate(dcrShift):
341  templateImage += applyDcr(self[subfilter][bbox], dcr, warpCtrl)
342  if mask is not None:
343  templateImage.setMask(mask[bbox])
344  return templateImage
345 
346  def buildMatchedExposure(self, exposure=None, warpCtrl=None,
347  visitInfo=None, bbox=None, wcs=None, mask=None):
348  """Wrapper to create an exposure from a template image.
349 
350  Parameters
351  ----------
352  exposure : `lsst.afw.image.Exposure`, optional
353  The input exposure to build a matched template for.
354  May be omitted if all of the metadata is supplied separately
355  warpCtrl : `lsst.afw.Math.WarpingControl`
356  Configuration settings for warping an image
357  visitInfo : `lsst.afw.image.VisitInfo`, optional
358  Metadata for the exposure. Ignored if ``exposure`` is set.
359  bbox : `lsst.afw.geom.Box2I`, optional
360  Sub-region of the coadd. Ignored if ``exposure`` is set.
361  wcs : `lsst.afw.geom.SkyWcs`, optional
362  Coordinate system definition (wcs) for the exposure.
363  Ignored if ``exposure`` is set.
364  mask : `lsst.afw.image.Mask`, optional
365  reference mask to use for the template image.
366 
367  Returns
368  -------
369  templateExposure : `lsst.afw.image.exposureF`
370  The DCR-matched template
371  """
372  templateImage = self.buildMatchedTemplate(exposure, warpCtrl, visitInfo, bbox, wcs, mask)
373  templateExposure = afwImage.ExposureF(bbox, wcs)
374  templateExposure.setMaskedImage(templateImage)
375  templateExposure.setPsf(self.psf)
376  templateExposure.setFilter(self.filter)
377  return templateExposure
378 
379  def conditionDcrModel(self, modelImages, bbox, gain=1.):
380  """Average two iterations' solutions to reduce oscillations.
381 
382  Parameters
383  ----------
384  modelImages : `list` of `lsst.afw.image.MaskedImage`
385  The new DCR model images from the current iteration.
386  The values will be modified in place.
387  bbox : `lsst.afw.geom.Box2I`
388  Sub-region of the coadd
389  gain : `float`, optional
390  Relative weight to give the new solution when updating the model.
391  Defaults to 1.0, which gives equal weight to both solutions.
392  """
393  # Calculate weighted averages of the image and variance planes.
394  # Note that ``newModel *= gain`` would multiply the variance by ``gain**2``
395  for model, newModel in zip(self, modelImages):
396  newModel.image *= gain
397  newModel.image += model[bbox].image
398  newModel.image /= 1. + gain
399  newModel.variance *= gain
400  newModel.variance += model[bbox].variance
401  newModel.variance /= 1. + gain
402 
403  def regularizeModelIter(self, subfilter, newModel, bbox, regularizationFactor,
404  regularizationWidth=2):
405  """Restrict large variations in the model between iterations.
406 
407  Parameters
408  ----------
409  subfilter : `int`
410  Index of the current subfilter within the full band.
411  newModel : `lsst.afw.image.MaskedImage`
412  The new DCR model for one subfilter from the current iteration.
413  Values in ``newModel`` that are extreme compared with the last
414  iteration are modified in place.
415  bbox : `lsst.afw.geom.Box2I`
416  Sub-region to coadd
417  regularizationFactor : `float`
418  Maximum relative change of the model allowed between iterations.
419  regularizationWidth : int, optional
420  Minimum radius of a region to include in regularization, in pixels.
421  """
422  refImage = self[subfilter][bbox].image.array
423  highThreshold = np.abs(refImage)*regularizationFactor
424  lowThreshold = refImage/regularizationFactor
425  self.applyImageThresholds(newModel, highThreshold=highThreshold, lowThreshold=lowThreshold,
426  regularizationWidth=regularizationWidth)
427 
428  def regularizeModelFreq(self, modelImages, bbox, regularizationFactor,
429  regularizationWidth=2):
430  """Restrict large variations in the model between subfilters.
431 
432  Parameters
433  ----------
434  modelImages : `list` of `lsst.afw.image.MaskedImage`
435  The new DCR model images from the current iteration.
436  The values will be modified in place.
437  bbox : `lsst.afw.geom.Box2I`
438  Sub-region to coadd
439  regularizationFactor : `float`
440  Maximum relative change of the model allowed between subfilters.
441  regularizationWidth : `int`, optional
442  Minimum radius of a region to include in regularization, in pixels.
443  """
444  # ``regularizationFactor`` is the maximum change between subfilter images, so the maximum difference
445  # between one subfilter image and the average will be the square root of that.
446  maxDiff = np.sqrt(regularizationFactor)
447  refImage = self.getReferenceImage(bbox)
448 
449  for model in modelImages:
450  highThreshold = np.abs(refImage)*maxDiff
451  lowThreshold = refImage/maxDiff
452  self.applyImageThresholds(model[bbox], highThreshold=highThreshold, lowThreshold=lowThreshold,
453  regularizationWidth=regularizationWidth)
454 
455  def calculateNoiseCutoff(self, maskedImage, statsCtrl, bufferSize,
456  convergenceMaskPlanes="DETECTED", mask=None, bbox=None):
457  """Helper function to calculate the background noise level of an image.
458 
459  Parameters
460  ----------
461  maskedImage : `lsst.afw.image.MaskedImage`
462  The input image to evaluate the background noise properties.
463  statsCtrl : `lsst.afw.math.StatisticsControl`
464  Statistics control object for coaddition.
465  bufferSize : `int`
466  Number of additional pixels to exclude
467  from the edges of the bounding box.
468  convergenceMaskPlanes : `list` of `str`, or `str`
469  Mask planes to use to calculate convergence.
470  mask : `lsst.afw.image.Mask`, Optional
471  Optional alternate mask
472  bbox : `lsst.afw.geom.Box2I`, optional
473  Sub-region of the masked image to calculate the noise level over.
474 
475  Returns
476  -------
477  noiseCutoff : `float`
478  The threshold value to treat pixels as noise in an image..
479  """
480  if bbox is None:
481  bbox = self.bbox
482  if mask is None:
483  mask = maskedImage[bbox].mask
484  bboxShrink = afwGeom.Box2I(bbox)
485  bboxShrink.grow(-bufferSize)
486  convergeMask = mask.getPlaneBitMask(convergenceMaskPlanes)
487 
488  backgroundPixels = mask[bboxShrink].array & (statsCtrl.getAndMask() | convergeMask) == 0
489  noiseCutoff = np.std(maskedImage[bboxShrink].image.array[backgroundPixels])
490  return noiseCutoff
491 
492  def applyImageThresholds(self, maskedImage, highThreshold=None, lowThreshold=None,
493  regularizationWidth=2):
494  """Restrict image values to be between upper and lower limits.
495 
496  This method flags all pixels in an image that are outside of the given
497  threshold values. The threshold values are taken from a reference image,
498  so noisy pixels are likely to get flagged. In order to exclude those
499  noisy pixels, the array of flags is eroded and dilated, which removes
500  isolated pixels outside of the thresholds from the list of pixels to be
501  modified. Pixels that remain flagged after this operation have their
502  values set to the appropriate upper or lower threshold value.
503 
504  Parameters
505  ----------
506  maskedImage : `lsst.afw.image.MaskedImage`
507  The image to apply the thresholds to.
508  The image plane values will be modified in place.
509  highThreshold : `numpy.ndarray`, optional
510  Array of upper limit values for each pixel of ``maskedImage``.
511  lowThreshold : `numpy.ndarray`, optional
512  Array of lower limit values for each pixel of ``maskedImage``.
513  regularizationWidth : `int`, optional
514  Minimum radius of a region to include in regularization, in pixels.
515  """
516  # Generate the structure for binary erosion and dilation, which is used to remove noise-like pixels.
517  # Groups of pixels with a radius smaller than ``regularizationWidth``
518  # will be excluded from regularization.
519  image = maskedImage.image.array
520  filterStructure = ndimage.iterate_structure(ndimage.generate_binary_structure(2, 1),
521  regularizationWidth)
522  if highThreshold is not None:
523  highPixels = image > highThreshold
524  if regularizationWidth > 0:
525  # Erode and dilate ``highPixels`` to exclude noisy pixels.
526  highPixels = ndimage.morphology.binary_opening(highPixels, structure=filterStructure)
527  image[highPixels] = highThreshold[highPixels]
528  if lowThreshold is not None:
529  lowPixels = image < lowThreshold
530  if regularizationWidth > 0:
531  # Erode and dilate ``lowPixels`` to exclude noisy pixels.
532  lowPixels = ndimage.morphology.binary_opening(lowPixels, structure=filterStructure)
533  image[lowPixels] = lowThreshold[lowPixels]
534 
535 
536 def applyDcr(maskedImage, dcr, warpCtrl, bbox=None, useInverse=False):
537  """Shift a masked image.
538 
539  Parameters
540  ----------
541  maskedImage : `lsst.afw.image.MaskedImage`
542  The input masked image to shift.
543  dcr : `lsst.afw.geom.Extent2I`
544  Shift calculated with ``calculateDcr``.
545  warpCtrl : `lsst.afw.math.WarpingControl`
546  Configuration settings for warping an image
547  bbox : `lsst.afw.geom.Box2I`, optional
548  Sub-region of the masked image to shift.
549  Shifts the entire image if None (Default).
550  useInverse : `bool`, optional
551  Use the reverse of ``dcr`` for the shift. Default: False
552 
553  Returns
554  -------
555  `lsst.afw.image.maskedImageF`
556  A masked image, with the pixels within the bounding box shifted.
557  """
558  padValue = afwImage.pixel.SinglePixelF(0., maskedImage.mask.getPlaneBitMask("NO_DATA"), 0)
559  if bbox is None:
560  bbox = maskedImage.getBBox()
561  shiftedImage = afwImage.MaskedImageF(bbox)
562  transform = makeTransform(AffineTransform((-1.0 if useInverse else 1.0)*dcr))
563  afwMath.warpImage(shiftedImage, maskedImage[bbox],
564  transform, warpCtrl, padValue=padValue)
565  return shiftedImage
566 
567 
568 def calculateDcr(visitInfo, wcs, filterInfo, dcrNumSubfilters):
569  """Calculate the shift in pixels of an exposure due to DCR.
570 
571  Parameters
572  ----------
573  visitInfo : `lsst.afw.image.VisitInfo`
574  Metadata for the exposure.
575  wcs : `lsst.afw.geom.SkyWcs`
576  Coordinate system definition (wcs) for the exposure.
577  filterInfo : `lsst.afw.image.Filter`
578  The filter definition, set in the current instruments' obs package.
579  dcrNumSubfilters : `int`
580  Number of sub-filters used to model chromatic effects within a band.
581 
582  Returns
583  -------
584  `lsst.afw.geom.Extent2I`
585  The 2D shift due to DCR, in pixels.
586  """
587  rotation = calculateImageParallacticAngle(visitInfo, wcs)
588  dcrShift = []
589  lambdaEff = filterInfo.getFilterProperty().getLambdaEff()
590  for wl0, wl1 in wavelengthGenerator(filterInfo, dcrNumSubfilters):
591  # Note that diffRefractAmp can be negative, since it's relative to the midpoint of the full band
592  diffRefractAmp0 = differentialRefraction(wl0, lambdaEff,
593  elevation=visitInfo.getBoresightAzAlt().getLatitude(),
594  observatory=visitInfo.getObservatory(),
595  weather=visitInfo.getWeather())
596  diffRefractAmp1 = differentialRefraction(wl1, lambdaEff,
597  elevation=visitInfo.getBoresightAzAlt().getLatitude(),
598  observatory=visitInfo.getObservatory(),
599  weather=visitInfo.getWeather())
600  diffRefractAmp = (diffRefractAmp0 + diffRefractAmp1)/2.
601  diffRefractPix = diffRefractAmp.asArcseconds()/wcs.getPixelScale().asArcseconds()
602  dcrShift.append(afwGeom.Extent2D(diffRefractPix*np.cos(rotation.asRadians()),
603  diffRefractPix*np.sin(rotation.asRadians())))
604  return dcrShift
605 
606 
607 def calculateImageParallacticAngle(visitInfo, wcs):
608  """Calculate the total sky rotation angle of an exposure.
609 
610  Parameters
611  ----------
612  visitInfo : `lsst.afw.image.VisitInfo`
613  Metadata for the exposure.
614  wcs : `lsst.afw.geom.SkyWcs`
615  Coordinate system definition (wcs) for the exposure.
616 
617  Returns
618  -------
619  `lsst.geom.Angle`
620  The rotation of the image axis, East from North.
621  Equal to the parallactic angle plus any additional rotation of the
622  coordinate system.
623  A rotation angle of 0 degrees is defined with
624  North along the +y axis and East along the +x axis.
625  A rotation angle of 90 degrees is defined with
626  North along the +x axis and East along the -y axis.
627  """
628  parAngle = visitInfo.getBoresightParAngle().asRadians()
629  cd = wcs.getCdMatrix()
630  cdAngle = (np.arctan2(-cd[0, 1], cd[0, 0]) + np.arctan2(cd[1, 0], cd[1, 1]))/2.
631  rotAngle = (cdAngle + parAngle)*radians
632  return rotAngle
633 
634 
635 def wavelengthGenerator(filterInfo, dcrNumSubfilters):
636  """Iterate over the wavelength endpoints of subfilters.
637 
638  Parameters
639  ----------
640  filterInfo : `lsst.afw.image.Filter`
641  The filter definition, set in the current instruments' obs package.
642  dcrNumSubfilters : `int`
643  Number of sub-filters used to model chromatic effects within a band.
644 
645  Yields
646  ------
647  `tuple` of two `float`
648  The next set of wavelength endpoints for a subfilter, in nm.
649  """
650  lambdaMin = filterInfo.getFilterProperty().getLambdaMin()
651  lambdaMax = filterInfo.getFilterProperty().getLambdaMax()
652  wlStep = (lambdaMax - lambdaMin)/dcrNumSubfilters
653  for wl in np.linspace(lambdaMin, lambdaMax, dcrNumSubfilters, endpoint=False):
654  yield (wl, wl + wlStep)
def fromImage(cls, maskedImage, dcrNumSubfilters, filterInfo=None, psf=None)
Definition: dcrModel.py:73
def calculateNoiseCutoff(self, maskedImage, statsCtrl, bufferSize, convergenceMaskPlanes="DETECTED", mask=None, bbox=None)
Definition: dcrModel.py:456
def __setitem__(self, subfilter, maskedImage)
Definition: dcrModel.py:184
def applyImageThresholds(self, maskedImage, highThreshold=None, lowThreshold=None, regularizationWidth=2)
Definition: dcrModel.py:493
def buildMatchedTemplate(self, exposure=None, warpCtrl=None, visitInfo=None, bbox=None, wcs=None, mask=None)
Definition: dcrModel.py:292
def calculateImageParallacticAngle(visitInfo, wcs)
Definition: dcrModel.py:607
int warpImage(DestImageT &destImage, SrcImageT const &srcImage, geom::TransformPoint2ToPoint2 const &srcToDest, WarpingControl const &control, typename DestImageT::SinglePixel padValue=lsst::afw::math::edgePixel< DestImageT >(typename lsst::afw::image::detail::image_traits< DestImageT >::image_category()))
def regularizeModelIter(self, subfilter, newModel, bbox, regularizationFactor, regularizationWidth=2)
Definition: dcrModel.py:404
def regularizeModelFreq(self, modelImages, bbox, regularizationFactor, regularizationWidth=2)
Definition: dcrModel.py:429
def __getitem__(self, subfilter)
Definition: dcrModel.py:159
def __init__(self, modelImages, filterInfo=None, psf=None)
Definition: dcrModel.py:66
def wavelengthGenerator(filterInfo, dcrNumSubfilters)
Definition: dcrModel.py:635
def buildMatchedExposure(self, exposure=None, warpCtrl=None, visitInfo=None, bbox=None, wcs=None, mask=None)
Definition: dcrModel.py:347
def calculateDcr(visitInfo, wcs, filterInfo, dcrNumSubfilters)
Definition: dcrModel.py:568
def fromDataRef(cls, dataRef, datasetType="dcrCoadd", numSubfilters=None, kwargs)
Definition: dcrModel.py:114
def getReferenceImage(self, bbox=None)
Definition: dcrModel.py:252
def assign(self, dcrSubModel, bbox=None)
Definition: dcrModel.py:268
def conditionDcrModel(self, modelImages, bbox, gain=1.)
Definition: dcrModel.py:379
def applyDcr(maskedImage, dcr, warpCtrl, bbox=None, useInverse=False)
Definition: dcrModel.py:536