24 from scipy
import ndimage
29 __all__ = [
"DcrModel",
"applyDcr",
"calculateDcr",
"calculateImageParallacticAngle"]
33 """A model of the true sky after correcting chromatic effects.
37 dcrNumSubfilters : `int`
38 Number of sub-filters used to model chromatic effects within a band.
39 modelImages : `list` of `lsst.afw.image.Image`
40 A list of masked images, each containing the model for one subfilter
44 The ``DcrModel`` contains an estimate of the true sky, at a higher
45 wavelength resolution than the input observations. It can be forward-
46 modeled to produce Differential Chromatic Refraction (DCR) matched
47 templates for a given ``Exposure``, and provides utilities for conditioning
48 the model in ``dcrAssembleCoadd`` to avoid oscillating solutions between
49 iterations of forward modeling or between the subfilters of the model.
52 def __init__(self, modelImages, filterInfo=None, psf=None, mask=None, variance=None, photoCalib=None):
62 def fromImage(cls, maskedImage, dcrNumSubfilters, filterInfo=None, psf=None, photoCalib=None):
63 """Initialize a DcrModel by dividing a coadd between the subfilters.
67 maskedImage : `lsst.afw.image.MaskedImage`
68 Input coadded image to divide equally between the subfilters.
69 dcrNumSubfilters : `int`
70 Number of sub-filters used to model chromatic effects within a band.
71 filterInfo : `lsst.afw.image.Filter`, optional
72 The filter definition, set in the current instruments' obs package.
73 Required for any calculation of DCR, including making matched templates.
74 psf : `lsst.afw.detection.Psf`, optional
75 Point spread function (PSF) of the model.
76 Required if the ``DcrModel`` will be persisted.
77 photoCalib : `lsst.afw.image.PhotoCalib`, optional
78 Calibration to convert instrumental flux and
79 flux error to nanoJansky.
83 dcrModel : `lsst.pipe.tasks.DcrModel`
84 Best fit model of the true sky after correcting chromatic effects.
89 If there are any unmasked NAN values in ``maskedImage``.
93 model = maskedImage.image.clone()
94 mask = maskedImage.mask.clone()
100 variance = maskedImage.variance.clone()
101 variance /= dcrNumSubfilters
102 model /= dcrNumSubfilters
103 modelImages = [model, ]
104 for subfilter
in range(1, dcrNumSubfilters):
105 modelImages.append(model.clone())
106 return cls(modelImages, filterInfo, psf, mask, variance, photoCalib=photoCalib)
109 def fromDataRef(cls, dataRef, datasetType="dcrCoadd", numSubfilters=None, **kwargs):
110 """Load an existing DcrModel from a Gen 2 repository.
114 dataRef : `lsst.daf.persistence.ButlerDataRef`
115 Data reference defining the patch for coaddition and the
117 datasetType : `str`, optional
118 Name of the DcrModel in the registry {"dcrCoadd", "dcrCoadd_sub"}
119 numSubfilters : `int`
120 Number of sub-filters used to model chromatic effects within a band.
122 Additional keyword arguments to pass to look up the model in the data registry.
123 Common keywords and their types include: ``tract``:`str`, ``patch``:`str`,
124 ``bbox``:`lsst.afw.geom.Box2I`
128 dcrModel : `lsst.pipe.tasks.DcrModel`
129 Best fit model of the true sky after correcting chromatic effects.
137 if "subfilter" in kwargs:
138 kwargs.pop(
"subfilter")
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()
145 psf = dcrCoadd.getPsf()
149 variance = dcrCoadd.variance
150 if photoCalib
is None:
151 photoCalib = dcrCoadd.getPhotoCalib()
152 modelImages.append(dcrCoadd.image)
153 return cls(modelImages, filterInfo, psf, mask, variance, photoCalib)
157 """Load an existing DcrModel from a Gen 3 repository.
161 availableCoaddRefs : `dict` of `int` : `lsst.daf.butler.DeferredDatasetHandle`
162 Dictionary of spatially relevant retrieved coadd patches,
163 indexed by their sequential patch number.
167 dcrModel : `lsst.pipe.tasks.DcrModel`
168 Best fit model of the true sky after correcting chromatic effects.
175 modelImages = [
None]*len(availableCoaddRefs)
177 for coaddRef
in availableCoaddRefs:
178 subfilter = coaddRef.dataId[
"subfilter"]
179 dcrCoadd = coaddRef.get()
180 if filterInfo
is None:
181 filterInfo = dcrCoadd.getFilter()
183 psf = dcrCoadd.getPsf()
187 variance = dcrCoadd.variance
188 if photoCalib
is None:
189 photoCalib = dcrCoadd.getPhotoCalib()
190 modelImages[subfilter] = dcrCoadd.image
191 return cls(modelImages, filterInfo, psf, mask, variance, photoCalib)
194 """Return the number of subfilters.
198 dcrNumSubfilters : `int`
199 The number of DCR subfilters in the model.
204 """Iterate over the subfilters of the DCR model.
209 Index of the current ``subfilter`` within the full band.
210 Negative indices are allowed, and count in reverse order
211 from the highest ``subfilter``.
215 modelImage : `lsst.afw.image.Image`
216 The DCR model for the given ``subfilter``.
221 If the requested ``subfilter`` is greater or equal to the number
222 of subfilters in the model.
224 if np.abs(subfilter) >= len(self):
225 raise IndexError(
"subfilter out of bounds.")
229 """Update the model image for one subfilter.
234 Index of the current subfilter within the full band.
235 maskedImage : `lsst.afw.image.Image`
236 The DCR model to set for the given ``subfilter``.
241 If the requested ``subfilter`` is greater or equal to the number
242 of subfilters in the model.
244 If the bounding box of the new image does not match.
246 if np.abs(subfilter) >= len(self):
247 raise IndexError(
"subfilter out of bounds.")
248 if maskedImage.getBBox() != self.
bbox:
249 raise ValueError(
"The bounding box of a subfilter must not change.")
254 """Return the filter of the model.
258 filter : `lsst.afw.image.Filter`
259 The filter definition, set in the current instruments' obs package.
265 """Return the psf of the model.
269 psf : `lsst.afw.detection.Psf`
270 Point spread function (PSF) of the model.
276 """Return the common bounding box of each subfilter image.
280 bbox : `lsst.afw.geom.Box2I`
281 Bounding box of the DCR model.
283 return self[0].getBBox()
287 """Return the common mask of each subfilter image.
291 mask : `lsst.afw.image.Mask`
292 Mask plane of the DCR model.
298 """Return the common variance of each subfilter image.
302 variance : `lsst.afw.image.Image`
303 Variance plane of the DCR model.
308 """Calculate a reference image from the average of the subfilter images.
312 bbox : `lsst.afw.geom.Box2I`, optional
313 Sub-region of the coadd. Returns the entire image if `None`.
317 refImage : `numpy.ndarray`
318 The reference image with no chromatic effects applied.
320 bbox = bbox
or self.
bbox
321 return np.mean([model[bbox].array
for model
in self], axis=0)
323 def assign(self, dcrSubModel, bbox=None):
324 """Update a sub-region of the ``DcrModel`` with new values.
328 dcrSubModel : `lsst.pipe.tasks.DcrModel`
329 New model of the true scene after correcting chromatic effects.
330 bbox : `lsst.afw.geom.Box2I`, optional
331 Sub-region of the coadd.
332 Defaults to the bounding box of ``dcrSubModel``.
337 If the new model has a different number of subfilters.
339 if len(dcrSubModel) != len(self):
340 raise ValueError(
"The number of DCR subfilters must be the same "
341 "between the old and new models.")
342 bbox = bbox
or self.
bbox
343 for model, subModel
in zip(self, dcrSubModel):
344 model.assign(subModel[bbox], bbox)
347 visitInfo=None, bbox=None, wcs=None, mask=None,
348 splitSubfilters=True, splitThreshold=0., amplifyModel=1.):
349 """Create a DCR-matched template image for an exposure.
353 exposure : `lsst.afw.image.Exposure`, optional
354 The input exposure to build a matched template for.
355 May be omitted if all of the metadata is supplied separately
356 order : `int`, optional
357 Interpolation order of the DCR shift.
358 visitInfo : `lsst.afw.image.VisitInfo`, optional
359 Metadata for the exposure. Ignored if ``exposure`` is set.
360 bbox : `lsst.afw.geom.Box2I`, optional
361 Sub-region of the coadd. Ignored if ``exposure`` is set.
362 wcs : `lsst.afw.geom.SkyWcs`, optional
363 Coordinate system definition (wcs) for the exposure.
364 Ignored if ``exposure`` is set.
365 mask : `lsst.afw.image.Mask`, optional
366 reference mask to use for the template image.
367 splitSubfilters : `bool`, optional
368 Calculate DCR for two evenly-spaced wavelengths in each subfilter,
369 instead of at the midpoint. Default: True
370 splitThreshold : `float`, optional
371 Minimum DCR difference within a subfilter required to use ``splitSubfilters``
372 amplifyModel : `float`, optional
373 Multiplication factor to amplify differences between model planes.
374 Used to speed convergence of iterative forward modeling.
378 templateImage : `lsst.afw.image.ImageF`
379 The DCR-matched template
384 If neither ``exposure`` or all of ``visitInfo``, ``bbox``, and ``wcs`` are set.
387 raise ValueError(
"'filterInfo' must be set for the DcrModel in order to calculate DCR.")
388 if exposure
is not None:
389 visitInfo = exposure.getInfo().getVisitInfo()
390 bbox = exposure.getBBox()
391 wcs = exposure.getInfo().getWcs()
392 elif visitInfo
is None or bbox
is None or wcs
is None:
393 raise ValueError(
"Either exposure or visitInfo, bbox, and wcs must be set.")
394 dcrShift =
calculateDcr(visitInfo, wcs, self.
filter, len(self), splitSubfilters=splitSubfilters)
395 templateImage = afwImage.ImageF(bbox)
397 for subfilter, dcr
in enumerate(dcrShift):
399 model = (self[subfilter][bbox].array - refModel)*amplifyModel + refModel
401 model = self[subfilter][bbox].array
402 templateImage.array +=
applyDcr(model, dcr, splitSubfilters=splitSubfilters,
403 splitThreshold=splitThreshold, order=order)
407 visitInfo=None, bbox=None, wcs=None, mask=None):
408 """Wrapper to create an exposure from a template image.
412 exposure : `lsst.afw.image.Exposure`, optional
413 The input exposure to build a matched template for.
414 May be omitted if all of the metadata is supplied separately
415 visitInfo : `lsst.afw.image.VisitInfo`, optional
416 Metadata for the exposure. Ignored if ``exposure`` is set.
417 bbox : `lsst.afw.geom.Box2I`, optional
418 Sub-region of the coadd. Ignored if ``exposure`` is set.
419 wcs : `lsst.afw.geom.SkyWcs`, optional
420 Coordinate system definition (wcs) for the exposure.
421 Ignored if ``exposure`` is set.
422 mask : `lsst.afw.image.Mask`, optional
423 reference mask to use for the template image.
427 templateExposure : `lsst.afw.image.exposureF`
428 The DCR-matched template
431 bbox = exposure.getBBox()
433 bbox=bbox, wcs=wcs, mask=mask)
434 maskedImage = afwImage.MaskedImageF(bbox)
435 maskedImage.image = templateImage[bbox]
436 maskedImage.mask = self.
mask[bbox]
437 maskedImage.variance = self.
variance[bbox]
441 templateExposure = afwImage.ExposureF(bbox, wcs)
442 templateExposure.setMaskedImage(maskedImage[bbox])
443 templateExposure.setPsf(self.
psf)
444 templateExposure.setFilter(self.
filter)
446 raise RuntimeError(
"No PhotoCalib set for the DcrModel. "
447 "If the DcrModel was created from a masked image"
448 " you must also specify the photoCalib.")
449 templateExposure.setPhotoCalib(self.
photoCalib)
450 return templateExposure
453 """Average two iterations' solutions to reduce oscillations.
457 modelImages : `list` of `lsst.afw.image.Image`
458 The new DCR model images from the current iteration.
459 The values will be modified in place.
460 bbox : `lsst.afw.geom.Box2I`
461 Sub-region of the coadd
462 gain : `float`, optional
463 Relative weight to give the new solution when updating the model.
464 Defaults to 1.0, which gives equal weight to both solutions.
467 for model, newModel
in zip(self, modelImages):
469 newModel += model[bbox]
470 newModel /= 1. + gain
473 regularizationWidth=2):
474 """Restrict large variations in the model between iterations.
479 Index of the current subfilter within the full band.
480 newModel : `lsst.afw.image.Image`
481 The new DCR model for one subfilter from the current iteration.
482 Values in ``newModel`` that are extreme compared with the last
483 iteration are modified in place.
484 bbox : `lsst.afw.geom.Box2I`
486 regularizationFactor : `float`
487 Maximum relative change of the model allowed between iterations.
488 regularizationWidth : int, optional
489 Minimum radius of a region to include in regularization, in pixels.
491 refImage = self[subfilter][bbox].array
492 highThreshold = np.abs(refImage)*regularizationFactor
493 lowThreshold = refImage/regularizationFactor
494 newImage = newModel.array
496 regularizationWidth=regularizationWidth)
499 regularizationWidth=2, mask=None, convergenceMaskPlanes="DETECTED"):
500 """Restrict large variations in the model between subfilters.
504 modelImages : `list` of `lsst.afw.image.Image`
505 The new DCR model images from the current iteration.
506 The values will be modified in place.
507 bbox : `lsst.afw.geom.Box2I`
509 statsCtrl : `lsst.afw.math.StatisticsControl`
510 Statistics control object for coaddition.
511 regularizationFactor : `float`
512 Maximum relative change of the model allowed between subfilters.
513 regularizationWidth : `int`, optional
514 Minimum radius of a region to include in regularization, in pixels.
515 mask : `lsst.afw.image.Mask`, optional
516 Optional alternate mask
517 convergenceMaskPlanes : `list` of `str`, or `str`, optional
518 Mask planes to use to calculate convergence.
522 This implementation of frequency regularization restricts each subfilter
523 image to be a smoothly-varying function times a reference image.
527 maxDiff = np.sqrt(regularizationFactor)
528 noiseLevel = self.
calculateNoiseCutoff(modelImages[0], statsCtrl, bufferSize=5, mask=mask, bbox=bbox)
530 badPixels = np.isnan(referenceImage) | (referenceImage <= 0.)
531 if np.sum(~badPixels) == 0:
534 referenceImage[badPixels] = 0.
535 filterWidth = regularizationWidth
536 fwhm = 2.*filterWidth
539 smoothRef = ndimage.filters.gaussian_filter(referenceImage, filterWidth, mode=
'constant')
542 smoothRef += 3.*noiseLevel
544 lowThreshold = smoothRef/maxDiff
545 highThreshold = smoothRef*maxDiff
546 for model
in modelImages:
548 highThreshold=highThreshold,
549 lowThreshold=lowThreshold,
550 regularizationWidth=regularizationWidth)
551 smoothModel = ndimage.filters.gaussian_filter(model.array, filterWidth, mode=
'constant')
552 smoothModel += 3.*noiseLevel
553 relativeModel = smoothModel/smoothRef
556 relativeModel2 = ndimage.filters.gaussian_filter(relativeModel, filterWidth/alpha)
557 relativeModel += alpha*(relativeModel - relativeModel2)
558 model.array = relativeModel*referenceImage
561 convergenceMaskPlanes="DETECTED", mask=None, bbox=None):
562 """Helper function to calculate the background noise level of an image.
566 image : `lsst.afw.image.Image`
567 The input image to evaluate the background noise properties.
568 statsCtrl : `lsst.afw.math.StatisticsControl`
569 Statistics control object for coaddition.
571 Number of additional pixels to exclude
572 from the edges of the bounding box.
573 convergenceMaskPlanes : `list` of `str`, or `str`
574 Mask planes to use to calculate convergence.
575 mask : `lsst.afw.image.Mask`, Optional
576 Optional alternate mask
577 bbox : `lsst.afw.geom.Box2I`, optional
578 Sub-region of the masked image to calculate the noise level over.
582 noiseCutoff : `float`
583 The threshold value to treat pixels as noise in an image..
588 mask = self.
mask[bbox]
590 bboxShrink.grow(-bufferSize)
591 convergeMask = mask.getPlaneBitMask(convergenceMaskPlanes)
593 backgroundPixels = mask[bboxShrink].array & (statsCtrl.getAndMask() | convergeMask) == 0
594 noiseCutoff = np.std(image[bboxShrink].array[backgroundPixels])
598 """Restrict image values to be between upper and lower limits.
600 This method flags all pixels in an image that are outside of the given
601 threshold values. The threshold values are taken from a reference image,
602 so noisy pixels are likely to get flagged. In order to exclude those
603 noisy pixels, the array of flags is eroded and dilated, which removes
604 isolated pixels outside of the thresholds from the list of pixels to be
605 modified. Pixels that remain flagged after this operation have their
606 values set to the appropriate upper or lower threshold value.
610 image : `numpy.ndarray`
611 The image to apply the thresholds to.
612 The values will be modified in place.
613 highThreshold : `numpy.ndarray`, optional
614 Array of upper limit values for each pixel of ``image``.
615 lowThreshold : `numpy.ndarray`, optional
616 Array of lower limit values for each pixel of ``image``.
617 regularizationWidth : `int`, optional
618 Minimum radius of a region to include in regularization, in pixels.
623 filterStructure = ndimage.iterate_structure(ndimage.generate_binary_structure(2, 1),
625 if highThreshold
is not None:
626 highPixels = image > highThreshold
627 if regularizationWidth > 0:
629 highPixels = ndimage.morphology.binary_opening(highPixels, structure=filterStructure)
630 image[highPixels] = highThreshold[highPixels]
631 if lowThreshold
is not None:
632 lowPixels = image < lowThreshold
633 if regularizationWidth > 0:
635 lowPixels = ndimage.morphology.binary_opening(lowPixels, structure=filterStructure)
636 image[lowPixels] = lowThreshold[lowPixels]
639 def applyDcr(image, dcr, useInverse=False, splitSubfilters=False, splitThreshold=0.,
640 doPrefilter=True, order=3):
641 """Shift an image along the X and Y directions.
645 image : `numpy.ndarray`
646 The input image to shift.
648 Shift calculated with ``calculateDcr``.
649 Uses numpy axes ordering (Y, X).
650 If ``splitSubfilters`` is set, each element is itself a `tuple`
651 of two `float`, corresponding to the DCR shift at the two wavelengths.
652 Otherwise, each element is a `float` corresponding to the DCR shift at
653 the effective wavelength of the subfilter.
654 useInverse : `bool`, optional
655 Apply the shift in the opposite direction. Default: False
656 splitSubfilters : `bool`, optional
657 Calculate DCR for two evenly-spaced wavelengths in each subfilter,
658 instead of at the midpoint. Default: False
659 splitThreshold : `float`, optional
660 Minimum DCR difference within a subfilter required to use ``splitSubfilters``
661 doPrefilter : `bool`, optional
662 Spline filter the image before shifting, if set. Filtering is required,
663 so only set to False if the image is already filtered.
664 Filtering takes ~20% of the time of shifting, so if `applyDcr` will be
665 called repeatedly on the same image it is more efficient to precalculate
667 order : `int`, optional
668 The order of the spline interpolation, default is 3.
672 shiftedImage : `numpy.ndarray`
673 A copy of the input image with the specified shift applied.
676 prefilteredImage = ndimage.spline_filter(image, order=order)
678 prefilteredImage = image
680 shiftAmp = np.max(np.abs([_dcr0 - _dcr1
for _dcr0, _dcr1
in zip(dcr[0], dcr[1])]))
681 if shiftAmp >= splitThreshold:
683 shift = [-1.*s
for s
in dcr[0]]
684 shift1 = [-1.*s
for s
in dcr[1]]
688 shiftedImage = ndimage.shift(prefilteredImage, shift, prefilter=
False, order=order)
689 shiftedImage += ndimage.shift(prefilteredImage, shift1, prefilter=
False, order=order)
695 dcr = (np.mean(dcr[0]), np.mean(dcr[1]))
697 shift = [-1.*s
for s
in dcr]
700 shiftedImage = ndimage.shift(prefilteredImage, shift, prefilter=
False, order=order)
704 def calculateDcr(visitInfo, wcs, filterInfo, dcrNumSubfilters, splitSubfilters=False):
705 """Calculate the shift in pixels of an exposure due to DCR.
709 visitInfo : `lsst.afw.image.VisitInfo`
710 Metadata for the exposure.
711 wcs : `lsst.afw.geom.SkyWcs`
712 Coordinate system definition (wcs) for the exposure.
713 filterInfo : `lsst.afw.image.Filter`
714 The filter definition, set in the current instruments' obs package.
715 dcrNumSubfilters : `int`
716 Number of sub-filters used to model chromatic effects within a band.
717 splitSubfilters : `bool`, optional
718 Calculate DCR for two evenly-spaced wavelengths in each subfilter,
719 instead of at the midpoint. Default: False
723 dcrShift : `tuple` of two `float`
724 The 2D shift due to DCR, in pixels.
725 Uses numpy axes ordering (Y, X).
729 weight = [0.75, 0.25]
730 lambdaEff = filterInfo.getFilterProperty().getLambdaEff()
733 diffRefractAmp0 = differentialRefraction(wavelength=wl0, wavelengthRef=lambdaEff,
734 elevation=visitInfo.getBoresightAzAlt().getLatitude(),
735 observatory=visitInfo.getObservatory(),
736 weather=visitInfo.getWeather())
737 diffRefractAmp1 = differentialRefraction(wavelength=wl1, wavelengthRef=lambdaEff,
738 elevation=visitInfo.getBoresightAzAlt().getLatitude(),
739 observatory=visitInfo.getObservatory(),
740 weather=visitInfo.getWeather())
742 diffRefractPix0 = diffRefractAmp0.asArcseconds()/wcs.getPixelScale().asArcseconds()
743 diffRefractPix1 = diffRefractAmp1.asArcseconds()/wcs.getPixelScale().asArcseconds()
744 diffRefractArr = [diffRefractPix0*weight[0] + diffRefractPix1*weight[1],
745 diffRefractPix0*weight[1] + diffRefractPix1*weight[0]]
746 shiftX = [diffRefractPix*np.sin(rotation.asRadians())
for diffRefractPix
in diffRefractArr]
747 shiftY = [diffRefractPix*np.cos(rotation.asRadians())
for diffRefractPix
in diffRefractArr]
748 dcrShift.append(((shiftY[0], shiftX[0]), (shiftY[1], shiftX[1])))
750 diffRefractAmp = (diffRefractAmp0 + diffRefractAmp1)/2.
751 diffRefractPix = diffRefractAmp.asArcseconds()/wcs.getPixelScale().asArcseconds()
752 shiftX = diffRefractPix*np.sin(rotation.asRadians())
753 shiftY = diffRefractPix*np.cos(rotation.asRadians())
754 dcrShift.append((shiftY, shiftX))
759 """Calculate the total sky rotation angle of an exposure.
763 visitInfo : `lsst.afw.image.VisitInfo`
764 Metadata for the exposure.
765 wcs : `lsst.afw.geom.SkyWcs`
766 Coordinate system definition (wcs) for the exposure.
771 The rotation of the image axis, East from North.
772 Equal to the parallactic angle plus any additional rotation of the
774 A rotation angle of 0 degrees is defined with
775 North along the +y axis and East along the +x axis.
776 A rotation angle of 90 degrees is defined with
777 North along the +x axis and East along the -y axis.
779 parAngle = visitInfo.getBoresightParAngle().asRadians()
780 cd = wcs.getCdMatrix()
782 cdAngle = (np.arctan2(-cd[0, 1], cd[0, 0]) + np.arctan2(cd[1, 0], cd[1, 1]))/2.
783 rotAngle = (cdAngle + parAngle)*geom.radians
785 cdAngle = (np.arctan2(cd[0, 1], -cd[0, 0]) + np.arctan2(cd[1, 0], cd[1, 1]))/2.
786 rotAngle = (cdAngle - parAngle)*geom.radians
791 """Iterate over the wavelength endpoints of subfilters.
795 filterInfo : `lsst.afw.image.Filter`
796 The filter definition, set in the current instruments' obs package.
797 dcrNumSubfilters : `int`
798 Number of sub-filters used to model chromatic effects within a band.
802 `tuple` of two `float`
803 The next set of wavelength endpoints for a subfilter, in nm.
805 lambdaMin = filterInfo.getFilterProperty().getLambdaMin()
806 lambdaMax = filterInfo.getFilterProperty().getLambdaMax()
807 wlStep = (lambdaMax - lambdaMin)/dcrNumSubfilters
808 for wl
in np.linspace(lambdaMin, lambdaMax, dcrNumSubfilters, endpoint=
False):
809 yield (wl, wl + wlStep)