• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dstndstn / tractor / eb0726aa-a142-4b68-90f3-7800b758aa02

pending completion
eb0726aa-a142-4b68-90f3-7800b758aa02

push

circle-ci

Dustin Lang
eliminate use of np.int_

693 of 2149 branches covered (32.25%)

3 of 3 new or added lines in 1 file covered. (100.0%)

3330 of 7240 relevant lines covered (45.99%)

0.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

48.63
/tractor/patch.py
1
import numpy as np
1✔
2

3
class ModelMask(object):
1✔
4
    def __init__(self, x0, y0, *args):
1✔
5
        '''
6
        ModelMask(x0, y0, w, h)
7
        ModelMask(x0, y0, mask)
8

9
        *mask* is assumed to be a binary image, True for pixels we're
10
         interested in.
11
        '''
12
        self.x0 = x0
1✔
13
        self.y0 = y0
1✔
14
        if len(args) == 2:
1✔
15
            self.w, self.h = args
1✔
16
            self.mask = None
1✔
17
        elif len(args) == 1:
1!
18
            self.mask = args[0]
1✔
19
            self.h, self.w = self.mask.shape
1✔
20
        else:
21
            raise ValueError('Wrong number of arguments')
×
22

23
    def __str__(self):
1✔
24
        s = ('ModelMask: origin (%i,%i), w=%i, h=%i' %
×
25
             (self.x0, self.y0, self.w, self.h))
26
        if self.mask is None:
×
27
            return s
×
28
        return s + ', has mask'
×
29

30
    def __repr__(self):
1✔
31
        if self.mask is None:
×
32
            return ('ModelMask(%i,%i, w=%i, h=%i)' %
×
33
                    (self.x0, self.y0, self.w, self.h))
34
        else:
35
            return ('ModelMask(%i,%i, mask of shape w=%i, h=%i)' %
×
36
                    (self.x0, self.y0, self.w, self.h))
37

38
    @staticmethod
1✔
39
    def fromExtent(x0, x1, y0, y1):
1✔
40
        return ModelMask(x0, y0, x1 - x0, y1 - y0)
1✔
41

42
    @property
1✔
43
    def shape(self):
1✔
44
        return (self.h, self.w)
1✔
45

46
    @property
1✔
47
    def x1(self):
1✔
48
        return self.x0 + self.w
1✔
49

50
    @property
1✔
51
    def y1(self):
1✔
52
        return self.y0 + self.h
1✔
53

54
    @property
1✔
55
    def extent(self):
1✔
56
        return (self.x0, self.x0 + self.w, self.y0, self.y0 + self.h)
×
57

58
# Adds two patches, handling the case when one is None
59

60

61
def add_patches(pa, pb):
1✔
62
    p = pa
1✔
63
    if pb is not None:
1!
64
        if p is None:
1!
65
            p = pb
×
66
        else:
67
            p += pb
1✔
68
    return p
1✔
69

70

71
class Patch(object):
1✔
72
    '''
73
    An image patch; a subimage.  In the Tractor we use these to hold
74
    synthesized (ie, model) images.  The patch is a rectangular grid
75
    of pixels and it knows its offset (2-d position) in some larger
76
    image.
77

78
    This class overloads arithmetic operations (like add and multiply)
79
    relevant to synthetic image patches.
80
    '''
81

82
    def __init__(self, x0, y0, patch):
1✔
83
        self.x0 = x0
1✔
84
        self.y0 = y0
1✔
85
        self.patch = patch
1✔
86
        self.name = ''
1✔
87
        if patch is not None:
1!
88
            try:
1✔
89
                H, W = patch.shape
1✔
90
                self.size = H * W
1✔
91
            except:
×
92
                pass
×
93

94
    @property
1✔
95
    def y1(self):
1✔
96
        return self.y0 + self.patch.shape[0]
×
97

98
    @property
1✔
99
    def x1(self):
1✔
100
        return self.x0 + self.patch.shape[1]
×
101

102
    def __str__(self):
1✔
103
        s = 'Patch: '
1✔
104
        name = getattr(self, 'name', '')
1✔
105
        if len(name):
1✔
106
            s += name + ' '
1✔
107
        s += 'origin (%i,%i) ' % (self.x0, self.y0)
1✔
108
        if self.patch is not None:
1!
109
            (H, W) = self.patch.shape
1✔
110
            s += 'size (%i x %i)' % (W, H)
1✔
111
        else:
112
            s += '(no image)'
×
113
        return s
1✔
114

115
    def set(self, other):
1✔
116
        self.x0 = other.x0
×
117
        self.y0 = other.y0
×
118
        self.patch = other.patch
×
119
        self.name = other.name
×
120

121
    def trimToNonZero(self):
1✔
122
        if self.patch is None:
×
123
            return
×
124
        H, W = self.patch.shape
×
125
        if W == 0 or H == 0:
×
126
            return
×
127
        for x in range(W):
×
128
            if not np.all(self.patch[:, x] == 0):
×
129
                break
×
130
        x0 = x
×
131
        for x in range(W, 0, -1):
×
132
            if not np.all(self.patch[:, x - 1] == 0):
×
133
                break
×
134
            if x <= x0:
×
135
                break
×
136
        x1 = x
×
137

138
        for y in range(H):
×
139
            if not np.all(self.patch[y, :] == 0):
×
140
                break
×
141
        y0 = y
×
142
        for y in range(H, 0, -1):
×
143
            if not np.all(self.patch[y - 1, :] == 0):
×
144
                break
×
145
            if y <= y0:
×
146
                break
×
147
        y1 = y
×
148

149
        if x0 == 0 and y0 == 0 and x1 == W and y1 == H:
×
150
            return
×
151

152
        self.patch = self.patch[y0:y1, x0:x1]
×
153
        H, W = self.patch.shape
×
154
        if H == 0 or W == 0:
×
155
            self.patch = None
×
156
        self.x0 += x0
×
157
        self.y0 += y0
×
158

159
    def overlapsBbox(self, bbox):
1✔
160
        ext = self.getExtent()
1✔
161
        (x0, x1, y0, y1) = ext
1✔
162
        (ox0, ox1, oy0, oy1) = bbox
1✔
163
        if x0 >= ox1 or ox0 >= x1 or y0 >= oy1 or oy0 >= y1:
1!
164
            return False
×
165
        return True
1✔
166

167
    def hasBboxOverlapWith(self, other):
1✔
168
        oext = other.getExtent()
×
169
        return self.overlapsBbox(oext)
×
170

171
    def hasNonzeroOverlapWith(self, other):
1✔
172
        from astrometry.util.miscutils import get_overlapping_region
×
173
        if not self.hasBboxOverlapWith(other):
×
174
            return False
×
175
        ext = self.getExtent()
×
176
        (x0, x1, y0, y1) = ext
×
177
        oext = other.getExtent()
×
178
        (ox0, ox1, oy0, oy1) = oext
×
179
        ix, ox = get_overlapping_region(ox0, ox1 - 1, x0, x1 - 1)
×
180
        iy, oy = get_overlapping_region(oy0, oy1 - 1, y0, y1 - 1)
×
181
        ix = slice(ix.start - x0, ix.stop - x0)
×
182
        iy = slice(iy.start - y0, iy.stop - y0)
×
183
        sub = self.patch[iy, ix]
×
184
        osub = other.patch[oy, ox]
×
185
        assert(sub.shape == osub.shape)
×
186
        return np.sum(sub * osub) > 0.
×
187

188
    def getNonZeroMask(self):
1✔
189
        nz = (self.patch != 0)
×
190
        return Patch(self.x0, self.y0, nz)
×
191

192
    def __repr__(self):
1✔
193
        return str(self)
1✔
194

195
    def setName(self, name):
1✔
196
        self.name = name
1✔
197

198
    def getName(self):
1✔
199
        return self.name
×
200

201
    def copy(self):
1✔
202
        if self.patch is None:
×
203
            return Patch(self.x0, self.y0, None)
×
204
        return Patch(self.x0, self.y0, self.patch.copy())
×
205

206
    def getExtent(self, margin=0):
1✔
207
        '''
208
        Return (x0, x1, y0, y1) of the region covered by this patch;
209
        NON-inclusive upper bounds, ie, [x0, x1), [y0, y1).
210
        '''
211
        (h, w) = self.shape
1✔
212
        return (self.x0 - margin, self.x0 + w + margin,
1✔
213
                self.y0 - margin, self.y0 + h + margin)
214

215
    def getOrigin(self):
1✔
216
        return (self.x0, self.y0)
×
217

218
    def getPatch(self):
1✔
219
        return self.patch
×
220

221
    def getImage(self):
1✔
222
        return self.patch
1✔
223

224
    def getX0(self):
1✔
225
        return self.x0
1✔
226

227
    def getY0(self):
1✔
228
        return self.y0
1✔
229

230
    def clipTo(self, W, H):
1✔
231
        if self.patch is None:
1!
232
            return False
×
233
        if self.x0 >= W:
1!
234
            # empty
235
            self.patch = None
×
236
            return False
×
237
        if self.y0 >= H:
1!
238
            self.patch = None
×
239
            return False
×
240
        # debug
241
        #o0 = (self.x0, self.y0, self.patch.shape)
242
        if self.x0 < 0:
1!
243
            self.patch = self.patch[:, -self.x0:]
×
244
            self.x0 = 0
×
245
        if self.y0 < 0:
1!
246
            self.patch = self.patch[-self.y0:, :]
×
247
            self.y0 = 0
×
248
        # debug
249
        # S = self.patch.shape
250
        # if len(S) != 2:
251
        #     print 'clipTo: shape', self.patch.shape
252
        #     print 'original offset and patch shape:', o0
253
        #     print 'current offset and patch shape:', self.x0, self.y0, self.patch.shape
254

255
        (h, w) = self.patch.shape
1✔
256
        if (self.x0 + w) > W:
1!
257
            self.patch = self.patch[:, :(W - self.x0)]
×
258
        if (self.y0 + h) > H:
1!
259
            self.patch = self.patch[:(H - self.y0), :]
×
260

261
        assert(self.x0 >= 0)
1✔
262
        assert(self.y0 >= 0)
1✔
263
        (h, w) = self.shape
1✔
264
        assert(w <= W)
1✔
265
        assert(h <= H)
1✔
266
        assert(self.shape == self.patch.shape)
1✔
267
        return True
1✔
268

269
    def clipToRoi(self, x0, x1, y0, y1):
1✔
270
        if self.patch is None:
×
271
            return False
×
272
        if ((self.x0 >= x1) or (self.x1 <= x0) or
×
273
                (self.y0 >= y1) or (self.y1 <= y0)):
274
            # empty
275
            self.patch = None
×
276
            return False
×
277

278
        if self.x0 < x0:
×
279
            self.patch = self.patch[:, x0 - self.x0:]
×
280
            self.x0 = x0
×
281
        if self.y0 < y0:
×
282
            self.patch = self.patch[(y0 - self.y0):, :]
×
283
            self.y0 = y0
×
284
        (h, w) = self.shape
×
285
        if (self.x0 + w) > x1:
×
286
            self.patch = self.patch[:, :(x1 - self.x0)]
×
287
        if (self.y0 + h) > y1:
×
288
            self.patch = self.patch[:(y1 - self.y0), :]
×
289
        return True
×
290

291
    def getSlice(self, parent=None):
1✔
292
        if self.patch is None:
1!
293
            return ([], [])
×
294
        (ph, pw) = self.patch.shape
1✔
295
        if parent is not None:
1!
296
            (H, W) = parent.shape
1✔
297
            return (slice(np.clip(self.y0, 0, H), np.clip(self.y0 + ph, 0, H)),
1✔
298
                    slice(np.clip(self.x0, 0, W), np.clip(self.x0 + pw, 0, W)))
299
        return (slice(self.y0, self.y0 + ph),
×
300
                slice(self.x0, self.x0 + pw))
301

302
    def getSlices(self, shape):
1✔
303
        '''
304
        shape = (H,W).
305

306
        Returns (spatch, sparent), slices that yield the overlapping regions
307
        in this Patch and the given image.
308
        '''
309
        from astrometry.util.miscutils import get_overlapping_region
×
310
        (ph, pw) = self.shape
×
311
        (ih, iw) = shape
×
312
        (outx, inx) = get_overlapping_region(
×
313
            self.x0, self.x0 + pw - 1, 0, iw - 1)
314
        (outy, iny) = get_overlapping_region(
×
315
            self.y0, self.y0 + ph - 1, 0, ih - 1)
316
        if inx == [] or iny == []:
×
317
            return (slice(0, 0), slice(0, 0)), (slice(0, 0), slice(0, 0))
×
318
        return (iny, inx), (outy, outx)
×
319

320
    def getPixelIndices(self, parent, dtype=np.int32):
1✔
321
        if self.patch is None:
1!
322
            return np.array([], dtype)
×
323
        (h, w) = self.shape
1✔
324
        (H, W) = parent.shape
1✔
325
        return ( (np.arange(w, dtype=dtype) + dtype(self.x0))[np.newaxis, :] +
1✔
326
                ((np.arange(h, dtype=dtype) + dtype(self.y0)) * dtype(W))[:, np.newaxis]).ravel()
327

328
    plotnum = 0
1✔
329

330
    def addTo(self, img, scale=1.):
1✔
331
        from astrometry.util.miscutils import get_overlapping_region
1✔
332
        if self.patch is None:
1!
333
            return
×
334
        (ih, iw) = img.shape
1✔
335
        (ph, pw) = self.shape
1✔
336
        (outx, inx) = get_overlapping_region(
1✔
337
            self.x0, self.x0 + pw - 1, 0, iw - 1)
338
        (outy, iny) = get_overlapping_region(
1✔
339
            self.y0, self.y0 + ph - 1, 0, ih - 1)
340
        if inx == [] or iny == []:
1!
341
            return
×
342
        p = self.patch[iny, inx]
1✔
343
        img[outy, outx] += p * scale
1✔
344

345
        # if False:
346
        #   tmpimg = np.zeros_like(img)
347
        #   tmpimg[outy,outx] = p * scale
348
        #   plt.clf()
349
        #   plt.imshow(tmpimg, interpolation='nearest', origin='lower')
350
        #   plt.hot()
351
        #   plt.colorbar()
352
        #   fn = 'addto-%03i.png' % Patch.plotnum
353
        #   plt.savefig(fn)
354
        #   print 'Wrote', fn
355
        #
356
        #   plt.clf()
357
        #   plt.imshow(p, interpolation='nearest', origin='lower')
358
        #   plt.hot()
359
        #   plt.colorbar()
360
        #   fn = 'addto-%03i-p.png' % Patch.plotnum
361
        #   plt.savefig(fn)
362
        #   print 'Wrote', fn
363
        #
364
        #   Patch.plotnum += 1
365

366
    def __getattr__(self, name):
1✔
367
        if name == 'shape':
1!
368
            if self.patch is None:
1!
369
                return (0, 0)
×
370
            return self.patch.shape
1✔
371
        raise AttributeError('Patch: unknown attribute "%s"' % name)
×
372

373
    # Implement *=, /= for numeric types
374
    def __imul__(self, f):
1✔
375
        if self.patch is not None:
1!
376
            self.patch *= f
1✔
377
        return self
1✔
378

379
    def __idiv__(self, f):
1✔
380
        if self.patch is not None:
×
381
            self.patch /= f
×
382
        return self
×
383

384
    # Implement *, / for numeric types
385
    def __mul__(self, f):
1✔
386
        if self.patch is None:
1!
387
            return Patch(self.x0, self.y0, None)
×
388
        return Patch(self.x0, self.y0, self.patch * f)
1✔
389

390
    def __div__(self, f):
1✔
391
        if self.patch is None:
×
392
            return Patch(self.x0, self.y0, None)
×
393
        return Patch(self.x0, self.y0, self.patch / f)
×
394

395
    def performArithmetic(self, other, opname, otype=float):
1✔
396
        assert(isinstance(other, Patch))
1✔
397
        if (self.x0 == other.getX0() and self.y0 == other.getY0() and
1✔
398
                self.shape == other.shape):
399
            assert(self.x0 == other.getX0())
1✔
400
            assert(self.y0 == other.getY0())
1✔
401
            assert(self.shape == other.shape)
1✔
402
            if self.patch is None or other.patch is None:
1!
403
                return Patch(self.x0, self.y0, None)
×
404
            pcopy = self.patch.copy()
1✔
405
            op = getattr(pcopy, opname)
1✔
406
            return Patch(self.x0, self.y0, op(other.patch))
1✔
407

408
        (ph, pw) = self.patch.shape
1✔
409
        (ox0, oy0) = other.getX0(), other.getY0()
1✔
410
        (oh, ow) = other.shape
1✔
411

412
        # Find the union of the regions.
413
        ux0 = min(ox0, self.x0)
1✔
414
        uy0 = min(oy0, self.y0)
1✔
415
        ux1 = max(ox0 + ow, self.x0 + pw)
1✔
416
        uy1 = max(oy0 + oh, self.y0 + ph)
1✔
417

418
        # Set the "self" portion of the union
419
        p = np.zeros((uy1 - uy0, ux1 - ux0), dtype=otype)
1✔
420
        p[self.y0 - uy0: self.y0 - uy0 + ph,
1✔
421
          self.x0 - ux0: self.x0 - ux0 + pw] = self.patch
422

423
        # Get a slice for the "other"'s portion of the union
424
        psub = p[oy0 - uy0: oy0 - uy0 + oh,
1✔
425
                 ox0 - ux0: ox0 - ux0 + ow]
426
        # Perform the in-place += or -= operation on "psub"
427
        op = getattr(psub, opname)
1✔
428
        op(other.getImage())
1✔
429
        return Patch(ux0, uy0, p)
1✔
430

431
    def __add__(self, other):
1✔
432
        return self.performArithmetic(other, '__iadd__')
1✔
433

434
    def __sub__(self, other):
1✔
435
        return self.performArithmetic(other, '__isub__')
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc