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

keflavich / image_registration / 111

pending completion
111

Pull #8

travis-ci

keflavich
debug: py.test --version to make sure we're using the right version
Pull Request #8: Python 3.4 support

75 of 75 new or added lines in 17 files covered. (100.0%)

488 of 1400 relevant lines covered (34.86%)

0.35 hits per line

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

45.32
/image_registration/cross_correlation_shifts.py
1
from __future__ import print_function
1✔
2
try: 
1✔
3
    from AG_fft_tools import correlate2d,fast_ffts
1✔
4
except ImportError:
1✔
5
    from image_registration.fft_tools import correlate2d,fast_ffts
1✔
6
import warnings
1✔
7
import numpy as np
1✔
8

9
__all__ = ['cross_correlation_shifts','cross_correlation_shifts_FITS']
1✔
10

11
def cross_correlation_shifts(image1, image2, errim1=None, errim2=None,
1✔
12
        maxoff=None, verbose=False, gaussfit=False, return_error=False,
13
        zeromean=True, **kwargs):
14
    """ Use cross-correlation and a 2nd order taylor expansion to measure the
15
    offset between two images
16

17
    Given two images, calculate the amount image2 is offset from image1 to
18
    sub-pixel accuracy using 2nd order taylor expansion.
19

20
    Parameters
21
    ----------
22
    image1: np.ndarray
23
        The reference image
24
    image2: np.ndarray
25
        The offset image.  Must have the same shape as image1
26
    errim1: np.ndarray [optional]
27
        The pixel-by-pixel error on the reference image
28
    errim2: np.ndarray [optional]
29
        The pixel-by-pixel error on the offset image.  
30
    maxoff: int
31
        Maximum allowed offset (in pixels).  Useful for low s/n images that you
32
        know are reasonably well-aligned, but might find incorrect offsets due to 
33
        edge noise
34
    zeromean : bool
35
        Subtract the mean from each image before performing cross-correlation?
36
    verbose: bool
37
        Print out extra messages?
38
    gaussfit : bool
39
        Use a Gaussian fitter to fit the peak of the cross-correlation?
40
    return_error: bool
41
        Return an estimate of the error on the shifts.  WARNING: I still don't
42
        understand how to make these agree with simulations.
43
        The analytic estimate comes from
44
        http://adsabs.harvard.edu/abs/2003MNRAS.342.1291Z
45
        At high signal-to-noise, the analytic version overestimates the error
46
        by a factor of about 1.8, while the gaussian version overestimates
47
        error by about 1.15.  At low s/n, they both UNDERestimate the error.
48
        The transition zone occurs at a *total* S/N ~ 1000 (i.e., the total
49
        signal in the map divided by the standard deviation of the map - 
50
        it depends on how many pixels have signal)
51

52
    **kwargs are passed to correlate2d, which in turn passes them to convolve.
53
    The available options include image padding for speed and ignoring NaNs.
54

55
    References
56
    ----------
57
    From http://solarmuri.ssl.berkeley.edu/~welsch/public/software/cross_cor_taylor.pro
58

59
    Examples
60
    --------
61
    >>> import numpy as np
62
    >>> im1 = np.zeros([10,10])
63
    >>> im2 = np.zeros([10,10])
64
    >>> im1[4,3] = 1
65
    >>> im2[5,5] = 1
66
    >>> import image_registration
67
    >>> yoff,xoff = image_registration.cross_correlation_shifts(im1,im2)
68
    >>> im1_aligned_to_im2 = np.roll(np.roll(im1,int(yoff),1),int(xoff),0)
69
    >>> assert (im1_aligned_to_im2-im2).sum() == 0
70
    
71

72
    """
73

74
    if not image1.shape == image2.shape:
1✔
75
        raise ValueError("Images must have same shape.")
×
76

77
    if zeromean:
1✔
78
        image1 = image1 - (image1[image1==image1].mean())
1✔
79
        image2 = image2 - (image2[image2==image2].mean())
1✔
80

81
    image1 = np.nan_to_num(image1)
1✔
82
    image2 = np.nan_to_num(image2)
1✔
83

84
    quiet = kwargs.pop('quiet') if 'quiet' in kwargs else not verbose
1✔
85
    ccorr = (correlate2d(image1,image2,quiet=quiet,**kwargs) / image1.size)
1✔
86
    # allow for NaNs set by convolve (i.e., ignored pixels)
87
    ccorr[ccorr!=ccorr] = 0
1✔
88
    if ccorr.shape != image1.shape:
1✔
89
        raise ValueError("Cross-correlation image must have same shape as input images.  This can only be violated if you pass a strange kwarg to correlate2d.")
×
90

91
    ylen,xlen = image1.shape
1✔
92
    xcen = xlen/2-(1-xlen%2) 
1✔
93
    ycen = ylen/2-(1-ylen%2) 
1✔
94

95
    if ccorr.max() == 0:
1✔
96
        warnings.warn("WARNING: No signal found!  Offset is defaulting to 0,0")
×
97
        return 0,0
×
98

99
    if maxoff is not None:
1✔
100
        if verbose: print("Limiting maximum offset to %i" % maxoff)
×
101
        subccorr = ccorr[ycen-maxoff:ycen+maxoff+1,xcen-maxoff:xcen+maxoff+1]
×
102
        ymax,xmax = np.unravel_index(subccorr.argmax(), subccorr.shape)
×
103
        xmax = xmax+xcen-maxoff
×
104
        ymax = ymax+ycen-maxoff
×
105
    else:
106
        ymax,xmax = np.unravel_index(ccorr.argmax(), ccorr.shape)
1✔
107
        subccorr = ccorr
1✔
108

109
    if return_error:
1✔
110
        if errim1 is None:
×
111
            errim1 = np.ones(ccorr.shape) * image1[image1==image1].std() 
×
112
        if errim2 is None:
×
113
            errim2 = np.ones(ccorr.shape) * image2[image2==image2].std() 
×
114
        eccorr =( (correlate2d(errim1**2, image2**2,quiet=quiet,**kwargs)+
×
115
                   correlate2d(errim2**2, image1**2,quiet=quiet,**kwargs))**0.5 
116
                   / image1.size)
117
        if maxoff is not None:
×
118
            subeccorr = eccorr[ycen-maxoff:ycen+maxoff+1,xcen-maxoff:xcen+maxoff+1]
×
119
        else:
120
            subeccorr = eccorr
×
121

122
    if gaussfit:
1✔
123
        try: 
×
124
            from agpy import gaussfitter
×
125
        except ImportError:
×
126
            raise ImportError("Couldn't import agpy.gaussfitter; try using cross_correlation_shifts with gaussfit=False")
×
127
        if return_error:
×
128
            pars,epars = gaussfitter.gaussfit(subccorr,err=subeccorr,return_all=True)
×
129
            exshift = epars[2]
×
130
            eyshift = epars[3]
×
131
        else:
132
            pars,epars = gaussfitter.gaussfit(subccorr,return_all=True)
×
133
        xshift = maxoff - pars[2] if maxoff is not None else xcen - pars[2]
×
134
        yshift = maxoff - pars[3] if maxoff is not None else ycen - pars[3]
×
135
        if verbose: 
×
136
            print("Gaussian fit pars: ",xshift,yshift,epars[2],epars[3],pars[4],pars[5],epars[4],epars[5])
×
137

138
    else:
139

140
        xshift_int = xmax-xcen
1✔
141
        yshift_int = ymax-ycen
1✔
142

143
        local_values = ccorr[ymax-1:ymax+2,xmax-1:xmax+2]
1✔
144

145
        d1y,d1x = np.gradient(local_values)
1✔
146
        d2y,d2x,dxy = second_derivative(local_values)
1✔
147

148
        fx,fy,fxx,fyy,fxy = d1x[1,1],d1y[1,1],d2x[1,1],d2y[1,1],dxy[1,1]
1✔
149

150
        shiftsubx=(fyy*fx-fy*fxy)/(fxy**2-fxx*fyy)
1✔
151
        shiftsuby=(fxx*fy-fx*fxy)/(fxy**2-fxx*fyy)
1✔
152

153
        xshift = -(xshift_int+shiftsubx)
1✔
154
        yshift = -(yshift_int+shiftsuby)
1✔
155

156
        # http://adsabs.harvard.edu/abs/2003MNRAS.342.1291Z
157
        # Zucker error
158

159
        if return_error:
1✔
160
            #acorr1 = (correlate2d(image1,image1,quiet=quiet,**kwargs) / image1.size)
161
            #acorr2 = (correlate2d(image2,image2,quiet=quiet,**kwargs) / image2.size)
162
            #ccorrn = ccorr / eccorr**2 / ccorr.size #/ (errim1.mean()*errim2.mean()) #/ eccorr**2
163
            normalization = 1. / ((image1**2).sum()/image1.size) / ((image2**2).sum()/image2.size) 
×
164
            ccorrn = ccorr * normalization
×
165
            exshift = (np.abs(-1 * ccorrn.size * fxx*normalization/ccorrn[ymax,xmax] *
×
166
                    (ccorrn[ymax,xmax]**2/(1-ccorrn[ymax,xmax]**2)))**-0.5) 
167
            eyshift = (np.abs(-1 * ccorrn.size * fyy*normalization/ccorrn[ymax,xmax] *
×
168
                    (ccorrn[ymax,xmax]**2/(1-ccorrn[ymax,xmax]**2)))**-0.5) 
169
            if np.isnan(exshift):
×
170
                raise ValueError("Error: NAN error!")
×
171

172
    if return_error:
1✔
173
        return xshift,yshift,exshift,eyshift
×
174
    else:
175
        return xshift,yshift
1✔
176

177
def second_derivative(image):
1✔
178
    """
179
    Compute the second derivative of an image
180
    The derivatives are set to zero at the edges
181

182
    Parameters
183
    ----------
184
    image: np.ndarray
185

186
    Returns
187
    -------
188
    d/dx^2, d/dy^2, d/dxdy
189
    All three are np.ndarrays with the same shape as image.
190

191
    """
192
    shift_right = np.roll(image,1,1)
1✔
193
    shift_right[:,0] = 0
1✔
194
    shift_left = np.roll(image,-1,1)
1✔
195
    shift_left[:,-1] = 0
1✔
196
    shift_down = np.roll(image,1,0)
1✔
197
    shift_down[0,:] = 0
1✔
198
    shift_up = np.roll(image,-1,0)
1✔
199
    shift_up[-1,:] = 0
1✔
200

201
    shift_up_right = np.roll(shift_up,1,1)
1✔
202
    shift_up_right[:,0] = 0
1✔
203
    shift_down_left = np.roll(shift_down,-1,1)
1✔
204
    shift_down_left[:,-1] = 0
1✔
205
    shift_down_right = np.roll(shift_right,1,0)
1✔
206
    shift_down_right[0,:] = 0
1✔
207
    shift_up_left = np.roll(shift_left,-1,0)
1✔
208
    shift_up_left[-1,:] = 0
1✔
209

210
    dxx = shift_right+shift_left-2*image
1✔
211
    dyy = shift_up   +shift_down-2*image
1✔
212
    dxy=0.25*(shift_up_right+shift_down_left-shift_up_left-shift_down_right)
1✔
213

214
    return dxx,dyy,dxy
1✔
215

216
def cross_correlation_shifts_FITS(fitsfile1, fitsfile2,
1✔
217
        return_cropped_images=False, quiet=True, sigma_cut=False,
218
        register_method=cross_correlation_shifts, **kwargs):
219
    """
220
    Determine the shift between two FITS images using the cross-correlation
221
    technique.  Requires montage or hcongrid.
222

223
    Parameters
224
    ----------
225
    fitsfile1: str
226
        Reference fits file name
227
    fitsfile2: str
228
        Offset fits file name
229
    return_cropped_images: bool
230
        Returns the images used for the analysis in addition to the measured
231
        offsets
232
    quiet: bool
233
        Silence messages?
234
    sigma_cut: bool or int
235
        Perform a sigma-cut before cross-correlating the images to minimize
236
        noise correlation?
237
    """
238
    import montage
×
239
    try:
×
240
        import astropy.io.fits as pyfits
×
241
        import astropy.wcs as pywcs
×
242
    except ImportError:
×
243
        import pyfits
×
244
        import pywcs
×
245
    import tempfile
×
246

247
    header = pyfits.getheader(fitsfile1)
×
248
    temp_headerfile = tempfile.NamedTemporaryFile()
×
249
    header.toTxtFile(temp_headerfile.name)
×
250

251
    outfile = tempfile.NamedTemporaryFile()
×
252
    montage.wrappers.reproject(fitsfile2, outfile.name, temp_headerfile.name, exact_size=True, silent_cleanup=quiet)
×
253
    image2_projected = pyfits.getdata(outfile.name)
×
254
    image1 = pyfits.getdata(fitsfile1)
×
255
    
256
    outfile.close()
×
257
    temp_headerfile.close()
×
258

259
    if image1.shape != image2_projected.shape:
×
260
        raise ValueError("montage failed to reproject images to same shape.")
×
261

262
    if sigma_cut:
×
263
        corr_image1 = image1*(image1 > image1.std()*sigma_cut)
×
264
        corr_image2 = image2_projected*(image2_projected > image2_projected.std()*sigma_cut)
×
265
        OK = (corr_image1==corr_image1)*(corr_image2==corr_image2) 
×
266
        if (corr_image1[OK]*corr_image2[OK]).sum() == 0:
×
267
            print("Could not use sigma_cut of %f because it excluded all valid data" % sigma_cut)
×
268
            corr_image1 = image1
×
269
            corr_image2 = image2_projected
×
270
    else:
271
        corr_image1 = image1
×
272
        corr_image2 = image2_projected
×
273

274
    verbose = kwargs.pop('verbose') if 'verbose' in kwargs else not quiet
×
275
    xoff,yoff = register_method(corr_image1, corr_image2, verbose=verbose,**kwargs)
×
276
    
277
    wcs = pywcs.WCS(header)
×
278
    try:
×
279
        xoff_wcs,yoff_wcs = np.inner( np.array([[xoff,0],[0,yoff]]), wcs.wcs.cd )[[0,1],[0,1]]
×
280
    except AttributeError:
×
281
        xoff_wcs,yoff_wcs = 0,0
×
282

283
    if return_cropped_images:
×
284
        return xoff,yoff,xoff_wcs,yoff_wcs,image1,image2_projected
×
285
    else:
286
        return xoff,yoff,xoff_wcs,yoff_wcs
×
287
    
288

289

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

© 2024 Coveralls, Inc