Coverage for python/lsst/meas/algorithms/skyObjects.py: 30%

49 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-25 00:24 -0700

1 

2__all__ = ["SkyObjectsConfig", "SkyObjectsTask", "generateSkyObjects"] 

3 

4from scipy.stats import qmc 

5 

6from lsst.pex.config import Config, Field, ListField 

7from lsst.pipe.base import Task 

8 

9import lsst.afw.detection 

10import lsst.afw.geom 

11import lsst.afw.math 

12 

13 

14class SkyObjectsConfig(Config): 

15 """Configuration for generating sky objects""" 

16 avoidMask = ListField(dtype=str, default=["DETECTED", "DETECTED_NEGATIVE", "BAD", "NO_DATA"], 

17 doc="Avoid pixels masked with these mask planes") 

18 growMask = Field(dtype=int, default=0, 

19 doc="Number of pixels to grow the masked pixels when adding sky objects") 

20 sourceRadius = Field(dtype=float, default=8, doc="Radius, in pixels, of sky objects") 

21 nSources = Field(dtype=int, default=100, doc="Try to add this many sky objects") 

22 nTrialSources = Field(dtype=int, default=None, optional=True, 

23 doc="Maximum number of trial sky object positions\n" 

24 "(default: nSkySources*nTrialSkySourcesMultiplier)") 

25 nTrialSourcesMultiplier = Field(dtype=int, default=5, 

26 doc="Set nTrialSkySources to\n" 

27 " nSkySources*nTrialSkySourcesMultiplier\n" 

28 "if nTrialSkySources is None") 

29 

30 

31def generateSkyObjects(mask, seed, config): 

32 """Generate a list of Footprints of sky objects 

33 

34 Sky objects don't overlap with other objects. This is determined 

35 through the provided `mask` (in which objects are typically flagged 

36 as `DETECTED`). 

37 

38 Sky objects are positioned using a quasi-random Halton sequence number 

39 generator. This is a deterministic sequence that mimics a random trial and 

40 error approach whilst acting to minimize clustering of points for a given 

41 field of view. Up to `nTrialSources` points are generated, returning the 

42 first `nSources` that do not overlap with the mask. 

43 

44 Parameters 

45 ---------- 

46 mask : `lsst.afw.image.Mask` 

47 Input mask plane, which identifies pixels to avoid for the sky 

48 objects. 

49 seed : `int` 

50 Random number generator seed. 

51 config : `SkyObjectsConfig` 

52 Configuration for finding sky objects. 

53 

54 Returns 

55 ------- 

56 skyFootprints : `list` of `lsst.afw.detection.Footprint` 

57 Footprints of sky objects. Each will have a peak at the center 

58 of the sky object. 

59 """ 

60 if config.nSources <= 0: 

61 return [] 

62 

63 skySourceRadius = config.sourceRadius 

64 nSkySources = config.nSources 

65 nTrialSkySources = config.nTrialSources 

66 if nTrialSkySources is None: 

67 nTrialSkySources = config.nTrialSourcesMultiplier*nSkySources 

68 

69 box = mask.getBBox() 

70 box.grow(-(int(skySourceRadius) + 1)) # Avoid objects partially off the image 

71 xMin, yMin = box.getMin() 

72 xMax, yMax = box.getMax() 

73 

74 avoid = lsst.afw.geom.SpanSet.fromMask(mask, mask.getPlaneBitMask(config.avoidMask)) 

75 if config.growMask > 0: 

76 avoid = avoid.dilated(config.growMask) 

77 

78 sampler = qmc.Halton(d=2, seed=seed).random(nTrialSkySources) 

79 sample = qmc.scale(sampler, [xMin, yMin], [xMax, yMax]) 

80 

81 skyFootprints = [] 

82 for x, y in zip(sample[:, 0].astype(int), sample[:, 1].astype(int)): 

83 if len(skyFootprints) == nSkySources: 

84 break 

85 

86 spans = lsst.afw.geom.SpanSet.fromShape(int(skySourceRadius), offset=(x, y)) 

87 if spans.overlaps(avoid): 

88 continue 

89 

90 fp = lsst.afw.detection.Footprint(spans, mask.getBBox()) 

91 fp.addPeak(x, y, 0) 

92 skyFootprints.append(fp) 

93 

94 # Add doubled-in-size sky object spanSet to the avoid mask. 

95 avoid = avoid.union(spans.dilated(int(skySourceRadius))) 

96 

97 return skyFootprints 

98 

99 

100class SkyObjectsTask(Task): 

101 """Generate a list of Footprints of sky objects. 

102 """ 

103 ConfigClass = SkyObjectsConfig 

104 

105 def run(self, mask, seed): 

106 """Generate a list of Footprints of sky objects 

107 

108 Sky objects don't overlap with other objects. This is determined 

109 through the provided `mask` (in which objects are typically flagged 

110 as `DETECTED`). 

111 

112 Sky objects are positioned using a quasi-random Halton sequence 

113 number generator. This is a deterministic sequence that mimics a random 

114 trial and error approach whilst acting to minimize clustering of points 

115 for a given field of view. Up to `nTrialSources` points are generated, 

116 returning the first `nSources` that do not overlap with the mask. 

117 

118 Parameters 

119 ---------- 

120 mask : `lsst.afw.image.Mask` 

121 Input mask plane, which identifies pixels to avoid for the sky 

122 objects. 

123 seed : `int` 

124 Random number generator seed. 

125 

126 Returns 

127 ------- 

128 skyFootprints : `list` of `lsst.afw.detection.Footprint` 

129 Footprints of sky objects. Each will have a peak at the center 

130 of the sky object. 

131 """ 

132 skyFootprints = generateSkyObjects(mask, seed, self.config) 

133 self.log.info("Added %d of %d requested sky sources (%.0f%%)", len(skyFootprints), 

134 self.config.nSources, 100*len(skyFootprints)/self.config.nSources) 

135 return skyFootprints