aboutsummaryrefslogtreecommitdiffstats
path: root/astro
diff options
context:
space:
mode:
Diffstat (limited to 'astro')
-rwxr-xr-xastro/ps2d.py62
1 files changed, 51 insertions, 11 deletions
diff --git a/astro/ps2d.py b/astro/ps2d.py
index 3d20720..3241107 100755
--- a/astro/ps2d.py
+++ b/astro/ps2d.py
@@ -7,6 +7,26 @@
"""
Calculate the 2D cylindrical-averaged power spectrum from the
3D image spectral cube.
+
+References
+----------
+.. [liu2014]
+ Liu, Parsons & Trott 2014, PhRvD, 90, 023018
+ http://adsabs.harvard.edu/abs/2014PhRvD..90b3018L
+ Appendix.A
+
+.. [morales2004]
+ Morales & Hewitt 2004, ApJ, 615, 7
+ http://adsabs.harvard.edu/abs/2004ApJ...615....7M
+ Sec.3
+
+.. [matlab-psd-fft]
+ MATLAB - Power Spectral Density Estimates Using FFT
+ https://cn.mathworks.com/help/signal/ug/power-spectral-density-estimates-using-fft.html
+
+.. [matlab-answer-psd]
+ MATLAB Answers - How to create power spectral density from FFT
+ https://cn.mathworks.com/matlabcentral/answers/43548-how-to-create-power-spectral-density-from-fft-fourier-transform
"""
import os
@@ -52,9 +72,12 @@ def get_frequencies(wcs, nfreq):
class PS2D:
"""
- 2D cylindrical-averaged power spectrum
+ 2D cylindrically averaged power spectrum
- cube dimensions: [nfreq, height, width] / [Z, Y, X]
+ NOTE
+ ----
+ * Cube dimensions: [nfreq, height, width] <-> [Z, Y, X]
+ * Cube data unit: [K] (brightness temperature)
"""
def __init__(self, cube, pixelsize, frequencies,
window_name=None, window_width="extended"):
@@ -62,8 +85,13 @@ class PS2D:
self.cube = cube
self.pixelsize = pixelsize # [arcsec]
logger.info("Image pixel size: %.2f [arcsec]" % pixelsize)
- self.frequencies = np.array(frequencies) # [MHz]
+ self.frequencies = np.asarray(frequencies) # [MHz]
self.nfreq = len(self.frequencies)
+ self.dfreq = self.frequencies[1] - self.frequencies[0] # [MHz]
+ logger.info("Frequency band: %.2f-%.2f [MHz]" %
+ (self.frequencies.min(), self.frequencies.max()))
+ logger.info("Frequency channel width: %.2f [MHz], %d channels" %
+ (self.dfreq, self.nfreq))
# Central frequency and redshift
self.freqc = self.frequencies.mean()
self.zc = freq2z(self.freqc)
@@ -99,25 +127,37 @@ class PS2D:
return window
def pad_cube(self):
- # Pad the image cube to be square in spatial dimensions.
- # TODO
- __, ny, nz = self.cube.shape
- if ny != nz:
+ """
+ Pad the image cube to be square in spatial dimensions.
+ """
+ __, ny, nx = self.cube.shape
+ if nx != ny:
logger.info("Padding image to be square ...")
raise NotImplementedError
def calc_ps3d(self):
"""
Calculate the 3D power spectrum of the image cube.
+
+ The power spectrum is properly normalized to have dimension
+ of [K^2 Mpc^3].
"""
if self.window is not None:
logger.info("Applying window along frequency axis ...")
cube2 = self.cube * self.window[:, np.newaxis, np.newaxis]
else:
cube2 = self.cube.astype(np.float)
- logger.info("Calculating 3D FFT and PS ...")
+
+ logger.info("Calculating 3D FFT ...")
cubefft = fftpack.fftshift(fftpack.fftn(cube2))
- self.ps3d = np.abs(cubefft) ** 2
+
+ logger.info("Calculating 3D PS ...")
+ ps3d = np.abs(cubefft) ** 2 # [K^2]
+ # Normalization
+ norm1 = 1 / (self.Nx * self.Ny * self.Nz)
+ norm2 = 1 / (self.fs_xy**2 * self.fs_z) # [Mpc^3]
+ norm3 = 1 / (2*np.pi)**3
+ self.ps3d = ps3d * norm1 * norm2 * norm3 # [K^2 Mpc^3]
return self.ps3d
def calc_ps2d(self):
@@ -162,7 +202,7 @@ class PS2D:
hdu.writeto(outfile, overwrite=clobber)
except TypeError:
hdu.writeto(outfile, clobber=clobber)
- logger.info("PS2D results saved to file: %s" % outfile)
+ logger.info("Wrote 2D power spectrum to file: %s" % outfile)
@property
def k_xy(self):
@@ -249,7 +289,7 @@ class PS2D:
def main():
parser = argparse.ArgumentParser(
- description="Calculate 2D PS from 3D image cube")
+ description="Calculate 2D power spectrum from 3D image cube")
parser.add_argument("-C", "--clobber", dest="clobber",
action="store_true",
help="overwrite existing file")