Coverage for python/lsst/pipe/tasks/calexpCutout.py: 37%

49 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-07-16 11:48 +0000

1import lsst.pipe.base as pipeBase 

2import lsst.geom as geom 

3from lsst.pex.config import Field 

4from lsst.meas.algorithms import Stamp, Stamps 

5 

6__all__ = ['CalexpCutoutTaskConfig', 'CalexpCutoutTask'] 

7DETECTOR_DIMENSIONS = ('instrument', 'visit', 'detector') 

8 

9 

10class CalexpCutoutTaskConnections(pipeBase.PipelineTaskConnections, 

11 dimensions=DETECTOR_DIMENSIONS, 

12 defaultTemplates={}): 

13 """Connections class for CalexpCutoutTask 

14 """ 

15 in_table = pipeBase.connectionTypes.Input( 

16 doc="Locations for cutouts", 

17 name="cutout_positions", 

18 storageClass="AstropyQTable", 

19 dimensions=DETECTOR_DIMENSIONS, 

20 ) 

21 calexp = pipeBase.connectionTypes.Input( 

22 doc="Calexp objects", 

23 name="calexp", 

24 storageClass="ExposureF", 

25 dimensions=DETECTOR_DIMENSIONS, 

26 ) 

27 cutouts = pipeBase.connectionTypes.Output( 

28 doc="Cutouts", 

29 name="calexp_cutouts", 

30 storageClass="Stamps", 

31 dimensions=DETECTOR_DIMENSIONS, 

32 ) 

33 

34 

35class CalexpCutoutTaskConfig(pipeBase.PipelineTaskConfig, 

36 pipelineConnections=CalexpCutoutTaskConnections): 

37 """Configuration for CalexpCutoutTask 

38 """ 

39 max_cutouts = Field(dtype=int, default=100, doc='Maximum number of entries to process. ' 

40 'The result will be the first N in the input table.') 

41 skip_bad = Field(dtype=bool, default=True, doc='Skip cutouts that do not fall completely within' 

42 ' the calexp bounding box? If set to False a ValueError' 

43 ' is raised instead.') 

44 

45 

46class CalexpCutoutTask(pipeBase.PipelineTask): 

47 """Task for computing cutouts on a specific calexp given 

48 positions, xspans, and yspans of the stamps. 

49 """ 

50 ConfigClass = CalexpCutoutTaskConfig 

51 _DefaultName = "calexpCutoutTask" 

52 

53 def run(self, in_table, calexp): 

54 """Compute and return the cutouts. 

55 

56 Parameters 

57 ---------- 

58 in_table : `astropy.QTable` 

59 A table containing at least the following columns: position, xspan 

60 and yspan. The position should be an `astropy.SkyCoord`. The 

61 xspan and yspan are the extent of the cutout in x and y in pixels. 

62 calexp : `lsst.afw.image.ExposureF` 

63 The calibrated exposure from which to extract cutouts 

64 

65 Returns 

66 ------- 

67 output : `lsst.pipe.base.Struct` 

68 A struct containing: 

69 

70 * cutouts: an `lsst.meas.algorithms.Stamps` object 

71 that wraps a list of masked images of the cutouts and a 

72 `PropertyList` containing the metadata to be persisted 

73 with the cutouts. The exposure metadata is preserved and, 

74 in addition, arrays holding the RA and Dec of each stamp 

75 in degrees are added to the metadata. Note: the origin 

76 of the output stamps is `lsst.afw.image.PARENT`. 

77 * skipped_positions: a `list` of `lsst.geom.SpherePoint` objects for 

78 stamps that were skiped for being off the image 

79 or partially off the image 

80 

81 Raises 

82 ------ 

83 ValueError 

84 If the input catalog doesn't have the required columns, 

85 a ValueError is raised 

86 """ 

87 cols = in_table.colnames 

88 if 'position' not in cols or 'xspan' not in cols or 'yspan' not in cols: 

89 raise ValueError('Required column missing from the input table. ' 

90 'Required columns are "position", "xspan", and "yspan". ' 

91 f'The column names are: {in_table.colnames}') 

92 max_idx = self.config.max_cutouts 

93 cutout_list = [] 

94 wcs = calexp.getWcs() 

95 mim = calexp.getMaskedImage() 

96 ras = [] 

97 decs = [] 

98 skipped_positions = [] 

99 for rec in in_table[:max_idx]: 

100 ra = rec['position'].ra.degree 

101 dec = rec['position'].dec.degree 

102 ras.append(ra) 

103 decs.append(dec) 

104 pt = geom.SpherePoint(geom.Angle(ra, geom.degrees), 

105 geom.Angle(dec, geom.degrees)) 

106 pix = wcs.skyToPixel(pt) 

107 xspan = rec['xspan'].value 

108 yspan = rec['yspan'].value 

109 # Clamp to LL corner of the LL pixel and draw extent from there 

110 box = geom.Box2I(geom.Point2I(int(pix.x-xspan/2), int(pix.y-yspan/2)), 

111 geom.Extent2I(xspan, yspan)) 

112 if not mim.getBBox().contains(box): 

113 if not self.config.skip_bad: 

114 raise ValueError(f'Cutout bounding box is not completely contained in the image: {box}') 

115 else: 

116 skipped_positions.append(pt) 

117 continue 

118 sub = mim.Factory(mim, box) 

119 stamp = Stamp(stamp_im=sub, position=pt) 

120 cutout_list.append(stamp) 

121 metadata = calexp.getMetadata() 

122 metadata['RA_DEG'] = ras 

123 metadata['DEC_DEG'] = decs 

124 return pipeBase.Struct(cutouts=Stamps(cutout_list, metadata=metadata), 

125 skipped_positions=skipped_positions)