Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 and sizes 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, size. 

60 The position should be an `astropy.SkyCoord`. The size is 

61 the size of the cutout in pixels. All cutouts are square in pixel 

62 space. 

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

64 The calibrated exposure from which to extract cutouts 

65 

66 Returns 

67 ------- 

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

69 A struct containing: 

70 

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

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

73 `PropertyList` containing the metadata to be persisted 

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

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

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

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

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

79 stamps that were skiped for being off the image 

80 or partially off the image 

81 

82 Raises 

83 ------ 

84 ValueError 

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

86 a ValueError is raised 

87 """ 

88 if 'position' not in in_table.colnames or 'size' not in in_table.colnames: 

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

90 'Required columns are "position" and "size".' 

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 size = rec['size'].value 

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

109 box = geom.Box2I(geom.Point2I(int(pix.x-size/2), int(pix.y-size/2)), 

110 geom.Extent2I(size, size)) 

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

112 if not self.config.skip_bad: 

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

114 else: 

115 skipped_positions.append(pt) 

116 continue 

117 sub = mim.Factory(mim, box) 

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

119 cutout_list.append(stamp) 

120 metadata = calexp.getMetadata() 

121 metadata['RA_DEG'] = ras 

122 metadata['DEC_DEG'] = decs 

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

124 skipped_positions=skipped_positions)