Skip to content

Commit 23829d8

Browse files
committed
Documentation improved, test for image.reproject
1 parent 66dcd42 commit 23829d8

17 files changed

Lines changed: 725 additions & 160 deletions

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,6 @@ out.fits
2626
dirty.fits
2727
/doc
2828
/.ipynb_checkpoints
29+
psf.fits
30+
.DS_Store
31+
/data

‎CONTRIBUTORS.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Crocodile contributors (sorted alphabetically)
2+
==============================================
3+
4+
* **[Tim Cornwell](http://github.com/timcornwell)**
5+
6+
* Theory, Design, Implementation
7+
8+
* **[Bojan Nikolic](http://github.com/bnikolic)**
9+
10+
* Conception, Theory, Design, Implementation
11+
12+
* **[Peter Wortmann](http://github.com/scpmw)**
13+
14+
* Theory, Design, Implementation

‎arl/image.py‎

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@
44
# subclasses of astropy classes.
55
#
66

7-
import matplotlib.pyplot as plt
7+
import os
88

9+
import matplotlib.pyplot as plt
910
import numpy as numpy
10-
11-
from astropy.wcs import WCS
11+
from astropy.coordinates import SkyCoord
1212
from astropy.io import fits
13+
from astropy.wcs import WCS
1314
from astropy.wcs.utils import skycoord_to_pixel, pixel_to_skycoord
14-
from astropy.coordinates import SkyCoord
15+
from reproject import reproject_interp
16+
1517
from arl.skycomponent import SkyComponent, create_skycomponent
1618

1719
"""
1820
Functions that define and manipulate images. Images are just data and a World Coordinate System.
1921
"""
2022

23+
2124
class Image:
2225
"""Image class with Image data (as a numpy.array) and optionally the AstroPy WCS.
2326
@@ -31,13 +34,29 @@ class Image:
3134
- in numpy, the order is (frequency, polarisation, latitude, longitude)
3235
3336
"""
34-
37+
3538
def __init__(self):
3639
self.data = None
3740
self.wcs = None
3841

3942

40-
def image_show(im: Image, fig = None, title: str = ''):
43+
def create_test_image(canonical=True):
44+
"""Create a useful test image
45+
46+
This is the test image M31 widely used in ALMA and other simulations. It is actually part of an Halpha region in
47+
M31.
48+
49+
:param canonical: Make the image into a 4 dimensional image
50+
:returns: Image
51+
"""
52+
chome = os.environ['CROCODILE']
53+
im = image_from_fits("%s/data/models/M31.MOD" % chome)
54+
if canonical:
55+
im=image_replicate(im)
56+
return im
57+
58+
59+
def image_show(im: Image, fig=None, title: str = ''):
4160
""" Show an Image with coordinates using matplotlib
4261
4362
:param im:
@@ -52,7 +71,11 @@ def image_show(im: Image, fig = None, title: str = ''):
5271
fig = plt.figure()
5372
fig.add_subplot(111, projection=im.wcs.sub(['longitude', 'latitude']))
5473
plt.clf()
55-
plt.imshow(im.data[0, 0, :, :], origin='lower', cmap='rainbow')
74+
if len(im.data.shape) == 4:
75+
print(im.data[0, 0, :, :])
76+
plt.imshow(im.data[0, 0, :, :], origin='lower', cmap='rainbow')
77+
elif len(im.data.shape) == 2:
78+
plt.imshow(im.data[:, :], origin='lower', cmap='rainbow')
5679
plt.xlabel('RA---SIN')
5780
plt.ylabel('DEC--SIN')
5881
plt.title(title)
@@ -95,7 +118,7 @@ def image_to_fits(im: Image, fitsfile: str = 'imaging.fits'):
95118
:param fitsfile: Name of output fits file
96119
:type str:
97120
"""
98-
return fits.writeto(fitsfile, im.data, im.wcs.to_header(), clobber=True)
121+
return fits.writeto(filename=fitsfile, data=im.data, header=im.wcs.to_header(), clobber=True)
99122

100123

101124
def image_from_fits(fitsfile: str):
@@ -109,11 +132,12 @@ def image_from_fits(fitsfile: str):
109132
if fitsfile[0] == '.':
110133
import os
111134
chome = os.environ['CROCODILE']
112-
fitsfile="%s/%s" % (chome, fitsfile)
135+
fitsfile = "%s/%s" % (chome, fitsfile)
113136
hdulist = fits.open(fitsfile)
114137
fim = Image()
115138
fim.data = hdulist[0].data
116139
fim.wcs = WCS(fitsfile)
140+
hdulist.close()
117141
print("image_from_fits: Max, min in %s = %.6f, %.6f" % (fitsfile, fim.data.max(), fim.data.min()))
118142
return fim
119143

@@ -131,7 +155,7 @@ def image_add_wcs(im: Image, wcs: WCS):
131155
return im
132156

133157

134-
def image_replicate(im: Image, shape=None):
158+
def image_replicate(im: Image, shape=None, frequency=1.4e9):
135159
""" Make a new canonical shape Image, extended along third and fourth axes by replication.
136160
137161
The order is [chan, pol, dec, ra]
@@ -143,46 +167,47 @@ def image_replicate(im: Image, shape=None):
143167
:type 4-sequence:
144168
:returns: Image
145169
"""
146-
# TODO: Fill in extra axes in wcs
147-
if shape is None:
148-
shape = [1, 1, 1, 1]
170+
if shape == None:
171+
shape=[1,1,1,1]
172+
149173
if len(im.data.shape) == 2:
150174
fim = Image()
151-
image_add_wcs(fim, im.wcs)
175+
176+
newwcs = WCS(naxis=4)
177+
178+
newwcs.wcs.crpix = [im.wcs.wcs.crpix[0], im.wcs.wcs.crpix[1], 1.0, 1.0]
179+
newwcs.wcs.cdelt = [im.wcs.wcs.cdelt[0], im.wcs.wcs.cdelt[1], 1.0, 1.0]
180+
newwcs.wcs.crval = [im.wcs.wcs.crval[0], im.wcs.wcs.crval[1], 1.0, frequency]
181+
newwcs.wcs.ctype = [im.wcs.wcs.ctype[0], im.wcs.wcs.ctype[1], 'STOKES', 'FREQ']
182+
183+
image_add_wcs(fim, newwcs)
152184
fshape = [shape[3], shape[2], im.data.shape[1], im.data.shape[0]]
153185
fim.data = numpy.zeros(fshape)
154186
print("image_replicate: replicating shape %s to %s" % (im.data.shape, fim.data.shape))
155187
for i3 in range(shape[3]):
156188
for i2 in range(shape[2]):
157189
fim.data[i3, i2, :, :] = im.data[:, :]
158-
print(fim.wcs)
159-
elif len(im.data.shape) == 3:
160-
fim = Image()
161-
image_add_wcs(fim, im.wcs)
162-
# TODO: fix this for non-square images!
163-
fshape = [shape[3], im.data.shape[2], im.data.shape[1], im.data.shape[0]]
164-
fim.data = numpy.array(fshape)
165-
print("Image: replicating shape %s to %s" % (im.data.shape, fim.data.shape))
166-
for i3 in range(shape[3]):
167-
fim.data[i3, :, :, :] = im.data[:, :, :]
168190
else:
169191
fim = im
170-
192+
171193
return fim
172194

173195

174196
def image_reproject(im: Image, newwcs: WCS, shape=None):
175197
""" Reproject an image to a new coordinate system
198+
199+
Currently uses the reproject python package.
200+
TODO: Write tailored reproject routine
176201
177-
:param im:
202+
:param im: Image to be reprojected
178203
:type Image:
179204
:param newwcs: New WCS
180205
:type WCS:
181-
:returns: Image
206+
:returns: Reprojected Image, Footprint Image
182207
"""
183-
print("image_reproject: Not yet implemented")
184-
185-
return im
208+
rep, foot = reproject_interp((im.data, im.wcs), newwcs, shape, order='bicubic',
209+
independent_celestial_slices=False)
210+
return image_from_array(rep, newwcs), image_from_array(foot, newwcs)
186211

187212

188213
def image_FFT(im: Image, **kwargs):
@@ -211,6 +236,7 @@ def image_add(im1: Image, im2: Image, checkwcs=False):
211236
assert not checkwcs, "Checking WCS not yet implemented"
212237
return image_from_array(im1.data + im2.data, im1.wcs)
213238

239+
214240
def point_source_find(im: Image, **kwargs) -> SkyComponent:
215241
""" Find components in Image, return SkyComponent, just find the peak for now
216242
@@ -220,14 +246,14 @@ def point_source_find(im: Image, **kwargs) -> SkyComponent:
220246
"""
221247
# TODO: Implement full image fitting of components
222248
print("imaging.point_source_find: Finding components in Image")
223-
249+
224250
# Beware: The index sequencing is opposite in wcs and Python!
225251
locpeak = numpy.array(numpy.unravel_index((numpy.abs(im.data)).argmax(), im.data.shape))
226252
print("imaging.point_source_find: Found peak at pixel coordinates %s" % str(locpeak))
227253
w = im.wcs.sub(['longitude', 'latitude'])
228254
sc = pixel_to_skycoord(locpeak[3], locpeak[2], im.wcs, 0, 'wcs')
229255
print("imaging.point_source_find: Found peak at world coordinates %s" % str(sc))
230-
flux=im.data[:,:,locpeak[2],locpeak[3]]
256+
flux = im.data[:, :, locpeak[2], locpeak[3]]
231257
print("imaging.point_source_find: Flux is %s" % flux)
232258
# We also need the frequency values
233259
w = im.wcs.sub(['spectral'])
@@ -249,18 +275,20 @@ def flux_at_direction(im: Image, sc: SkyCoord, **kwargs) -> SkyComponent:
249275
w = im.wcs.sub(['longitude', 'latitude'])
250276
pixloc = skycoord_to_pixel(sc, im.wcs, 0, 'wcs')
251277
print("imaging.flux_at_direction: Extracting flux at pixel coordinates %d %d" % (pixloc[0], pixloc[1]))
252-
flux=im.data[:,:,int(pixloc[1]+0.5),int(pixloc[0]+0.5)]
278+
flux = im.data[:, :, int(pixloc[1] + 0.5), int(pixloc[0] + 0.5)]
253279
print("imaging.flux_at_direction: Flux is %s" % flux)
254-
280+
255281
# We also need the frequency values
256282
w = im.wcs.sub(['spectral'])
257283
frequency = w.wcs_pix2world(range(im.data.shape[0]), 0)
258-
284+
259285
return create_skycomponent(direction=sc, flux=flux, frequency=frequency, shape='point')
260286

287+
261288
if __name__ == '__main__':
262289
import os
263-
chome=os.environ['CROCODILE']
290+
291+
chome = os.environ['CROCODILE']
264292
kwargs = {}
265293
m31model = image_from_fits("%s/data/models/M31.MOD" % chome)
266294
m31model_by_array = image_from_array(m31model.data, m31model.wcs)

‎crocodile/clean.py‎

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
import numpy
44

5-
from .synthesis import sortw, doimg, wslicimg, wslicfwd
6-
7-
85
def overlapIndices(a1, a2,
96
shiftx, shifty):
107
""" Find the indices where two arrays overlapIndices
@@ -57,18 +54,12 @@ def hogbom(dirty,
5754
Hogbom CLEAN (1974A&AS...15..417H)
5855
5956
:param dirty: The dirty Image, i.e., the Image to be deconvolved
60-
6157
:param psf: The point spread-function
62-
6358
:param window: Regions where clean components are allowed. If True, all of the dirty Image is assumed to be
6459
allowed for clean components
65-
6660
:param gain: The "loop gain", i.e., the fraction of the brightest pixel that is removed in each iteration
67-
6861
:param thresh: Cleaning stops when the maximum of the absolute deviation of the residual is less than this value
69-
7062
:param niter: Maximum number of components to make if the threshold `thresh` is not hit
71-
7263
:returns clean SkyComponent Image, residual Image
7364
"""
7465

@@ -93,34 +84,3 @@ def hogbom(dirty,
9384
if numpy.fabs(res).max() < thresh:
9485
break
9586
return comps, res
96-
97-
98-
def majorcycle(T2, L2,
99-
p, v,
100-
gain,
101-
nmajor,
102-
nminor,
103-
wstep):
104-
"""Major cycle clean
105-
106-
:param v:
107-
:param gain:
108-
:param T2: Field of view in radians
109-
:param L2: Observing wavelength (m)
110-
:param p: UVWs of visiblities (m)
111-
:param nmajor: Number of major cycles
112-
:param nminor: Number of minor cycles
113-
:param wstep: Step in w (pixels)
114-
115-
NOT USED
116-
117-
"""
118-
ps, vs = sortw(p, v)
119-
for i in range(nmajor):
120-
dirty, psf, _ = doimg(T2, L2, ps, vs, lambda *x: wslicimg(*x, wstep=wstep, Qpx=1))
121-
cc, rres = hogbom(dirty, psf, True, gain, 0,
122-
nminor)
123-
xuv = numpy.fft.fftshift(numpy.fft.fft2(numpy.fft.ifftshift(cc)))
124-
ps, vsp = wslicfwd(xuv, T2, L2, p, wstep=wstep)
125-
vs = vs - vsp
126-
return ps, vs, cc, res

‎crocodile/msclean.py‎

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,63 +6,6 @@
66
from crocodile.clean import overlapIndices, argmax
77
from crocodile.synthesis import sortw, doimg, dopredict, simplepredict, simpleimg
88

9-
10-
def majorcycle(T2, L2,
11-
p, v,
12-
gain,
13-
nmajor,
14-
nminor,
15-
scales,
16-
thresh=0.0,
17-
fracthresh=0.1,
18-
predfn=simplepredict,
19-
imgfn=simpleimg):
20-
"""Major cycle for MultiScale Clean
21-
22-
:param gain:
23-
:param scales:
24-
:param thresh:
25-
:param fracthresh:
26-
:param predfn:
27-
:param imgfn:
28-
:param T2: Field of view in radians
29-
:param L2: Maximum uv (for setting Image pixel sizes)
30-
:param p: UVWs of visibilities (m)
31-
:param v: Values of visibilities
32-
:param nmajor: Number of major cycles
33-
:param nminor: Number of minor cycles
34-
:param thresh: Stopping threshold (for scale=0)
35-
:param fracthresh: Minor Cycle stopping threshold (for scale=0) fraction of peak
36-
37-
NOT USED
38-
"""
39-
40-
# The model is added to each major cycle and then the visibilities are
41-
# calculated from the full model
42-
global vsp
43-
ps, vso = sortw(p, v)
44-
dirty, psf, sumwt = doimg(T2, L2, ps, vso, imgfn)
45-
comps = 0.0 * dirty.copy()
46-
for i in range(nmajor):
47-
print("Start of major cycle %d" % i)
48-
cc, res = msclean(dirty, psf, True, gain, thresh, nminor, scales, fracthresh)
49-
plt.clf()
50-
plt.imshow(res, cmap='rainbow', origin='lower')
51-
plt.colorbar()
52-
plt.show()
53-
comps += cc
54-
# dopredict resorts the data
55-
pss, vsp = dopredict(T2, L2, ps, comps, predfn)
56-
vsr = vso - vsp
57-
dirty, psf, sumwt = doimg(T2, L2, ps, vsr, imgfn)
58-
if np.abs(dirty).max() < 1.1 * thresh:
59-
print("Reached stopping threshold %.6f Jy" % thresh)
60-
break
61-
print("End of major cycle")
62-
print("End of major cycles")
63-
return ps, vsp, comps, dirty
64-
65-
669
def msclean(dirty,
6710
psf,
6811
window,

0 commit comments

Comments
 (0)