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

1from lsst.geom import Point2I, Box2I, Extent2I 

2from .image import LOCAL, PARENT, ImageOrigin 

3 

4__all__ = ["supportSlicing"] 

5 

6 

7def splitSliceArgs(sliceArgs): 

8 """Separate the actual slice from origin arguments to __getitem__ or 

9 __setitem__, using PARENT for the origin if it is not provided. 

10 

11 Parameter 

12 --------- 

13 sliceArgs : `tuple`, `Box2I`, or `Point2I` 

14 The first argument passed to an image-like object's 

15 ``__getitem__`` or ``__setitem__``. 

16 

17 Returns 

18 ------- 

19 sliceArgs : `tuple`, `Box2I`, or `Point2I` 

20 The original sliceArgs passed in, with any ImageOrigin argument 

21 stripped off. 

22 origin : `ImageOrigin` 

23 Enum value that sets whether or not to consider xy0 in positions. 

24 

25 See interpretSliceArgs for more information. 

26 

27 Intended primarily for internal use by `supportSlicing()`. 

28 """ 

29 defaultOrigin = PARENT 

30 try: 

31 if isinstance(sliceArgs[-1], ImageOrigin): 

32 # Args are already a tuple that includes the origin. 

33 if len(sliceArgs) == 2: 

34 return sliceArgs[0], sliceArgs[-1] 

35 else: 

36 return sliceArgs[:-1], sliceArgs[-1] 

37 else: 

38 # Args are a tuple that does not include the origin; add it to make origin explicit. 

39 return sliceArgs, defaultOrigin 

40 except TypeError: # Arg is a scalar; return it along with the default origin. 

41 return sliceArgs, defaultOrigin 

42 

43 

44def handleNegativeIndex(index, size, origin, default): 

45 """Handle negative indices passed to image accessors. 

46 

47 When negative indices are used in LOCAL coordinates, we interpret them as 

48 relative to the upper bounds of the array, as in regular negative indexing 

49 in Python. 

50 

51 Using negative indices in PARENT coordinates is not allowed unless passed 

52 via a `Point2I` or `Box2I`; the potential for confusion between actual 

53 negative indices (when ``xy0 < 0``) and offsets relative to the upper 

54 bounds of the array is too great. 

55 

56 Parameters 

57 ---------- 

58 index : `int` or `None` 

59 1-d pixel index to interpret, as given by a caller to an image-like 

60 object's ``__getitem__`` or ``__setitem__``. 

61 size : `int` 

62 Size of the image in the dimension corresponding to ``index``. 

63 origin : `ImageOrigin` 

64 Enum value that sets whether or not to consider xy0 in positions. 

65 default : `int` 

66 Index to return if `index` is None. 

67 

68 Returns 

69 ------- 

70 index : `int` 

71 If ``origin==PARENT``, either the given ``index`` or ``default``. 

72 If ``origin==LOCAL``, an equivalent index guaranteed to be nonnegative. 

73 

74 Intended primarily for internal use by `supportSlicing()`. 

75 """ 

76 if index is None: 

77 assert default is not None 

78 return default 

79 if index < 0: 

80 if origin == LOCAL: 

81 index = size + index 

82 else: 

83 raise IndexError("Negative indices are not permitted with the PARENT origin. " 

84 "Use LOCAL to use negative to index relative to the end, " 

85 "and Point2I or Box2I indexing to access negative pixels " 

86 "in PARENT coordinates.") 

87 return index 

88 

89 

90def translateSliceArgs(sliceArgs, bboxGetter): 

91 """Transform a tuple of slice objects into a Box2I, correctly handling negative indices. 

92 

93 see `interpretSliceArgs` for a description of parameters 

94 

95 Returns 

96 ------- 

97 box : `Box2I` or `None` 

98 A box to use to create a subimage, or None if the slice refers to a 

99 scalar. 

100 index: `tuple` or `None` 

101 An ``(x, y)`` tuple of integers, or None if the slice refers to a 

102 box. 

103 origin : `ImageOrigin` 

104 Enum indicating whether to account for xy0. 

105 """ 

106 slices, origin = splitSliceArgs(sliceArgs) 

107 if isinstance(slices, Point2I): 

108 return None, slices, origin 

109 elif isinstance(slices, Box2I): 

110 return slices, None, origin 

111 

112 x, y, origin = interpretSliceArgs(sliceArgs, bboxGetter) 

113 

114 if isinstance(x, slice): 

115 assert isinstance(y, slice) 

116 if x.step is not None or y.step is not None: 

117 raise ValueError("Slices with steps are not supported in image indexing.") 

118 begin = Point2I(x.start, y.start) 

119 end = Point2I(x.stop, y.stop) 

120 return Box2I(begin, end - begin), None, origin 

121 

122 assert not isinstance(y, slice) 

123 return None, Point2I(x, y), origin 

124 

125 

126def interpretSliceArgs(sliceArgs, bboxGetter): 

127 """Transform arguments to __getitem__ or __setitem__ to a standard form. 

128 

129 Parameters 

130 ---------- 

131 sliceArgs : `tuple`, `Box2I`, or `Point2I` 

132 Slice arguments passed directly to `__getitem__` or `__setitem__`. 

133 bboxGetter : callable 

134 Callable that accepts an ImageOrigin enum value and returns the 

135 appropriate image bounding box. Usually the bound getBBox method 

136 of an Image, Mask, or MaskedImage object. 

137 

138 Returns 

139 ------- 

140 x : int or slice 

141 Index or slice in the x dimension 

142 y : int or slice 

143 Index or slice in the y dimension 

144 origin : `ImageOrigin` 

145 Either `PARENT` (coordinates respect XY0) or LOCAL 

146 (coordinates do not respect XY0) 

147 """ 

148 slices, origin = splitSliceArgs(sliceArgs) 

149 if isinstance(slices, Point2I): 

150 return slices.getX(), slices.getY(), origin 

151 elif isinstance(slices, Box2I): 

152 x0 = slices.getMinX() 

153 y0 = slices.getMinY() 

154 return slice(x0, x0 + slices.getWidth()), slice(y0, y0 + slices.getHeight()), origin 

155 elif isinstance(slices, slice): 

156 if slices.start is not None or slices.stop is not None or slices.step is not None: 

157 raise TypeError("Single-dimension slices must not have bounds.") 

158 x = slices 

159 y = slices 

160 origin = LOCAL # doesn't matter, as the slices cover the full image 

161 else: 

162 x, y = slices 

163 

164 bbox = bboxGetter(origin) 

165 if isinstance(x, slice): 

166 if isinstance(y, slice): 

167 xSlice = slice(handleNegativeIndex(x.start, bbox.getWidth(), origin, default=bbox.getBeginX()), 

168 handleNegativeIndex(x.stop, bbox.getWidth(), origin, default=bbox.getEndX())) 

169 ySlice = slice(handleNegativeIndex(y.start, bbox.getHeight(), origin, default=bbox.getBeginY()), 

170 handleNegativeIndex(y.stop, bbox.getHeight(), origin, default=bbox.getEndY())) 

171 return xSlice, ySlice, origin 

172 raise TypeError("Mixed indices of the form (slice, int) are not supported for images.") 

173 

174 if isinstance(y, slice): 

175 raise TypeError("Mixed indices of the form (int, slice) are not supported for images.") 

176 x = handleNegativeIndex(x, bbox.getWidth(), origin, default=None) 

177 y = handleNegativeIndex(y, bbox.getHeight(), origin, default=None) 

178 return x, y, origin 

179 

180 

181def imageIndicesToNumpy(sliceArgs, bboxGetter): 

182 """Convert slicing format to numpy 

183 

184 LSST `afw` image-like objects use an `[x,y]` coordinate 

185 convention, accept `Point2I` and `Box2I` 

186 objects for slicing, and slice relative to the 

187 bounding box `XY0` location; 

188 while python and numpy use the convention `[y,x]` 

189 with no `XY0`, so this method converts the `afw` 

190 indices or slices into numpy indices or slices 

191 

192 Parameters 

193 ---------- 

194 sliceArgs: `sequence`, `Point2I` or `Box2I` 

195 An `(xIndex, yIndex)` pair, or a single `(xIndex,)` tuple, 

196 where `xIndex` and `yIndex` can be a `slice` or `int`, 

197 or list of `int` objects, and if only a single `xIndex` is 

198 given, a `Point2I` or `Box2I`. 

199 bboxGetter : callable 

200 Callable that accepts an ImageOrigin enum value and returns the 

201 appropriate image bounding box. Usually the bound getBBox method 

202 of an Image, Mask, or MaskedImage object. 

203 

204 Returns 

205 ------- 

206 y: `int` or `slice` 

207 Index or `slice` in the y dimension 

208 x: `int` or `slice` 

209 Index or `slice` in the x dimension 

210 bbox: `Box2I` 

211 Bounding box of the image. 

212 If `bbox` is `None` then the result is a point and 

213 not a subset of an image. 

214 """ 

215 # Use a common slicing algorithm as single band images 

216 x, y, origin = interpretSliceArgs(sliceArgs, bboxGetter) 

217 x0 = bboxGetter().getMinX() 

218 y0 = bboxGetter().getMinY() 

219 

220 if origin == PARENT: 

221 if isinstance(x, slice): 

222 assert isinstance(y, slice) 

223 bbox = Box2I(Point2I(x.start, y.start), Extent2I(x.stop-x.start, y.stop-y.start)) 

224 x = slice(x.start - x0, x.stop - x0) 

225 y = slice(y.start - y0, y.stop - y0) 

226 else: 

227 x = x - x0 

228 y = y - y0 

229 bbox = None 

230 return y, x, bbox 

231 elif origin != LOCAL: 

232 raise ValueError("Unrecognized value for origin") 

233 

234 # Use a local bounding box 

235 if isinstance(x, slice): 

236 assert isinstance(y, slice) 

237 bbox = Box2I(Point2I(x.start + x0, y.start + y0), 

238 Extent2I(x.stop-x.start, y.stop-y.start)) 

239 else: 

240 bbox = None 

241 return y, x, bbox 

242 

243 

244def supportSlicing(cls): 

245 """Support image slicing 

246 """ 

247 

248 def Factory(self, *args, **kwargs): 

249 """Return an object of this type 

250 """ 

251 return cls(*args, **kwargs) 

252 cls.Factory = Factory 

253 

254 def clone(self): 

255 """Return a deep copy of self""" 

256 return cls(self, True) 

257 cls.clone = clone 

258 

259 def __getitem__(self, imageSlice): # noqa: N807 

260 box, index, origin = translateSliceArgs(imageSlice, self.getBBox) 

261 if box is not None: 

262 return self.subset(box, origin=origin) 

263 return self._get(index, origin=origin) 

264 cls.__getitem__ = __getitem__ 

265 

266 def __setitem__(self, imageSlice, rhs): # noqa: N807 

267 box, index, origin = translateSliceArgs(imageSlice, self.getBBox) 

268 if box is not None: 

269 if self.assign(rhs, box, origin) is NotImplemented: 

270 lhs = self.subset(box, origin=origin) 

271 lhs.set(rhs) 

272 else: 

273 self._set(index, origin=origin, value=rhs) 

274 cls.__setitem__ = __setitem__