Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1D analysis #159

Merged
merged 125 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
125 commits
Select commit Hold shift + click to select a range
134c5ab
CrossValidate: add model to self
jeffjennings Feb 20, 2023
95bf0e0
CrossValidate: rename var
jeffjennings Feb 20, 2023
7e18237
add onedim.py, func to get 1d vis fit
jeffjennings Feb 20, 2023
a5f6365
add 1d radial profile func
jeffjennings Feb 20, 2023
0c0ffc8
get_radial_profile: simplify args
jeffjennings Feb 20, 2023
aa5dd0c
typos
jeffjennings Feb 20, 2023
1d288c2
add deproject_vis routine
jeffjennings Feb 20, 2023
9b43c9b
setup.py: add new type of EXTRA, with frank
jeffjennings Feb 20, 2023
8ac919c
get_radial_profile: add comments, stage deprojec.
jeffjennings Feb 20, 2023
3055639
get_1d_vis_fit: stage vis deprojection
jeffjennings Feb 20, 2023
0b6da4b
move frank import internal to func
jeffjennings Feb 21, 2023
ce50793
get_radial_profile: bug fix (norm radial profile)
jeffjennings Feb 21, 2023
4f02353
Merge branch 'main' into one_dim
jeffjennings Feb 21, 2023
5437e0c
deproject_vis: add TODO
jeffjennings Feb 21, 2023
b4c318c
typo
jeffjennings Feb 22, 2023
dd7324f
get_1d_vis_fit: only intake `model`
jeffjennings Feb 22, 2023
a34374a
Merge branch 'main' into one_dim
jeffjennings Feb 22, 2023
8d93ec3
setup.py: copy/paste typo
jeffjennings Feb 22, 2023
a98f711
CrossValidate: hold off on hyperpar changes
jeffjennings Feb 22, 2023
b565fbc
Merge branch 'main' into one_dim
jeffjennings Feb 24, 2023
e4c121b
fold deproject_vis into get_1d_vis_fit
jeffjennings Feb 24, 2023
e30b3db
get_radial_profile: clean up docstring
jeffjennings Feb 24, 2023
6f2471b
get_radial_profile: convert to rad
jeffjennings Feb 24, 2023
df1ecdc
get_radial_profile: add 'geom' arg
jeffjennings Feb 24, 2023
eab4579
get_radial_profile: incorporate image deprojection
jeffjennings Feb 24, 2023
5c1f0e3
get_1d_vis_fit: update args, docstring
jeffjennings Feb 24, 2023
14c9239
get_1d_vis_fit: get model u, v, V
jeffjennings Feb 24, 2023
287e6c4
get_1d_vis_fit: deproject vis with frank
jeffjennings Feb 24, 2023
985fd9a
get_1d_vis_fit: get radial vis profile
jeffjennings Feb 24, 2023
6a47a0e
get_radial_profile: simplify
jeffjennings Feb 24, 2023
ed65449
get_radial_profile: simplify
jeffjennings Feb 28, 2023
8ed9563
get_1d_vis_fit debugged
jeffjennings Mar 13, 2023
aa7d9d9
flat_to_observer, observer_to_flat: opt_thick arg
jeffjennings Mar 13, 2023
35c7b93
mypy: add frank ignore
jeffjennings Mar 13, 2023
ef5633f
Merge branch 'main' into one_dim
jeffjennings Mar 13, 2023
beb3de1
revert opt_thick geometry arg
jeffjennings Mar 13, 2023
9fc9c26
typos
jeffjennings Mar 13, 2023
9661b9c
Merge branch 'main' into one_dim
jeffjennings Mar 22, 2023
d9f21a2
docstring spacing
jeffjennings Mar 22, 2023
c905d25
get_1d_vis_fit: intake vis and coords, not model
jeffjennings Mar 30, 2023
2ebd17e
get_1d_vis_profile: rename
jeffjennings Mar 30, 2023
349d9e7
get_1d_image_profile: rename
jeffjennings Mar 30, 2023
9405943
get_1d_image_profile: intake image and coords
jeffjennings Mar 30, 2023
7a36f25
get_1d_image_profile: add flux rescaling
jeffjennings Mar 30, 2023
090deaf
Merge branch 'main' into one_dim
jeffjennings Mar 30, 2023
4afe6d6
Merge branch 'main' into one_dim
jeffjennings Apr 4, 2023
ac01276
get_1d_vis_profile: explicit deproject
jeffjennings Jun 22, 2023
ad893f0
Merge branch 'one_dim' of https://github.com/MPoL-dev/MPoL into one_dim
jeffjennings Oct 17, 2023
883d4cc
Merge branch 'main' into one_dim
jeffjennings Oct 17, 2023
d6a2df3
Merge branch 'main' into one_dim
jeffjennings Oct 17, 2023
42aa022
Merge branch 'one_dim' of https://github.com/MPoL-dev/MPoL into one_dim
jeffjennings Oct 17, 2023
6a3a24f
blank space
jeffjennings Oct 17, 2023
6d06d9e
create 1d tests file
jeffjennings Oct 17, 2023
cf1c04d
rename 1d vis profile func
jeffjennings Oct 17, 2023
e3aedf9
radialV: force specify rescale_flux
jeffjennings Oct 17, 2023
0440d60
rename 1d brightness profile func
jeffjennings Oct 17, 2023
9e58207
radialI: force specify rescale_flux
jeffjennings Oct 17, 2023
446530e
radialI: simplify
jeffjennings Oct 17, 2023
1c5629c
remove reimplemented func
jeffjennings Oct 19, 2023
34c95b7
radialI: add TODO checks
jeffjennings Oct 19, 2023
f7efc90
start of onedim tests
jeffjennings Oct 19, 2023
8c7a9a0
radialI: remove 'rescale_flux'
jeffjennings Oct 27, 2023
25b4658
radialI: var disambiguation
jeffjennings Oct 27, 2023
d890a70
radialI: custom deprojection
jeffjennings Oct 27, 2023
843b0e2
radialI: more precise comments
jeffjennings Oct 27, 2023
2679a2f
radialI: sensible default bins
jeffjennings Oct 27, 2023
85e54a9
radialI: mask empty bins
jeffjennings Oct 27, 2023
c009f23
radialV: replicate radialI layout
jeffjennings Oct 27, 2023
10b7fb2
radialV: mask empty bins
jeffjennings Oct 27, 2023
6934dfa
radialI: more general bin guess
jeffjennings Oct 27, 2023
c768689
radialI: set bin guess
jeffjennings Oct 31, 2023
eacf2b6
Merge branch 'one_dim' of https://github.com/MPoL-dev/MPoL into one_dim
jeffjennings Oct 31, 2023
1b7fc03
Merge branch 'zenodo_relocate' of https://github.com/MPoL-dev/MPoL in…
jeffjennings Oct 31, 2023
5e4b192
add pytest fixtures for mock 1d dataset
jeffjennings Oct 31, 2023
9655277
add utility function to center a numpy image
jeffjennings Oct 31, 2023
062e2f1
add utility function to convert np to ImageCube
jeffjennings Oct 31, 2023
1b76a32
move np to ImageCube out of utils
jeffjennings Oct 31, 2023
efa6807
Move np to ImageCube into images.py
jeffjennings Oct 31, 2023
ec422f6
np__to_imagecube: add import
jeffjennings Oct 31, 2023
0ac11dc
Merge branch 'numpy_image_helper_funcs' of https://github.com/MPoL-de…
jeffjennings Nov 1, 2023
d99a025
add processing steps to mock_1d_image_models
jeffjennings Nov 1, 2023
343e377
mock 1d data: new filename
jeffjennings Nov 1, 2023
9ef73ef
simplify mock_1d_image_model fixture
jeffjennings Nov 1, 2023
bc21790
simplify onedim_test design
jeffjennings Nov 1, 2023
43f3311
implement test of radialI
jeffjennings Nov 1, 2023
eecfef4
add diag plot to test_radialI
jeffjennings Nov 1, 2023
c61f864
test_radialI: save diag fig
jeffjennings Nov 1, 2023
8f1a610
test_radialI: add plot ax labels
jeffjennings Nov 1, 2023
3cc2316
test_radialI: set bins
jeffjennings Nov 1, 2023
7380eca
radialV: less conservative step size
jeffjennings Nov 1, 2023
df6b2a6
mock_1d_image_model test fixture: clarify naming
jeffjennings Nov 1, 2023
704d770
add mock_1d_vis_model test fixture
jeffjennings Nov 1, 2023
346a598
remove unneeded func
jeffjennings Nov 1, 2023
647cee7
test_radialI: update test points
jeffjennings Nov 1, 2023
d83bf2a
test_radialV: add test assert
jeffjennings Nov 1, 2023
611fee0
test_radialV: add plot
jeffjennings Nov 1, 2023
80fd113
Merge branch 'zenodo_record_ref' of https://github.com/MPoL-dev/MPoL …
jeffjennings Nov 1, 2023
6a6894d
mock_1d_archive: update zenodo record ref
jeffjennings Nov 1, 2023
59ad2c7
test_radialI: consistent naming
jeffjennings Nov 1, 2023
f94c235
onedim_test: set geom
jeffjennings Nov 1, 2023
45e7b23
add frank as test dep
jeffjennings Nov 1, 2023
3a9f6b1
tests.yml: add frank to dep installs
jeffjennings Nov 1, 2023
35caea8
extra_requires: remove frank version restriction
jeffjennings Nov 1, 2023
c2c6d21
typo
jeffjennings Nov 1, 2023
afad0a9
test_radialI: clean up diag plot
jeffjennings Nov 2, 2023
55029c7
test_radialV: clean up diag plot
jeffjennings Nov 2, 2023
53da887
remove unneeded func
jeffjennings Nov 2, 2023
6885e61
Merge branch 'main' of https://github.com/MPoL-dev/MPoL into one_dim
jeffjennings Nov 2, 2023
ea9e970
conftest: recast 'geom' type
jeffjennings Nov 3, 2023
e4d0499
mock_1d_image_model: undo image centering
jeffjennings Nov 3, 2023
90f8fdc
mock_1d_image_model: place image in cube
jeffjennings Nov 3, 2023
bdd599d
mock_1d_image_model: update outputs
jeffjennings Nov 3, 2023
b50e98e
mock_1d_vis_model: create fourier cube
jeffjennings Nov 3, 2023
c313f10
mock_1d_vis_model: put vis in ground_cube
jeffjennings Nov 3, 2023
2209863
mock_1d_vis_model: update outputs
jeffjennings Nov 3, 2023
7bfb5c6
radialI: change input from array to icube
jeffjennings Nov 3, 2023
24b48cd
radialI: add chan arg
jeffjennings Nov 3, 2023
845bd28
radialI: update calls to icube
jeffjennings Nov 3, 2023
cf835cf
radialV: change input to fcube
jeffjennings Nov 3, 2023
943874e
radialV: add chan arg
jeffjennings Nov 3, 2023
052b4c8
radialV: use fcube
jeffjennings Nov 3, 2023
deeec2a
test_radialI: update data retrieval call
jeffjennings Nov 3, 2023
3493068
test_radialV: update data retrieval call
jeffjennings Nov 3, 2023
5bd168a
test_radialI: use icube
jeffjennings Nov 3, 2023
fb7bf32
test_radialV: use fcube
jeffjennings Nov 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
# in __init__. below we'll reinstall for the tests.
run: |
pip install astropy
pip install frank
pip install .
- name: Cache/Restore the .mpol folder cache
uses: actions/cache@v3
Expand Down
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ ignore_missing_imports = True
[mypy-torchkbnufft.*]
ignore_missing_imports = True

[mypy-frank.*]
ignore_missing_imports = True

[mypy-fast_histogram.*]
ignore_missing_imports = True
6 changes: 5 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def get_version(rel_path):
"astropy",
"tensorboard",
"mypy",
"frank>=1.2.1",
],
"docs": [
"sphinx>=2.3.0",
Expand All @@ -55,10 +56,13 @@ def get_version(rel_path):
"pyro-ppl",
"arviz[all]"
],
"analysis": [
"frank>=1.2.1",
],
}

EXTRA_REQUIRES["dev"] = (
EXTRA_REQUIRES["test"] + EXTRA_REQUIRES["docs"] + ["pylint", "black", "pre-commit"]
EXTRA_REQUIRES["test"] + EXTRA_REQUIRES["docs"] + EXTRA_REQUIRES["analysis"] + ["pylint", "black", "pre-commit"]
)


Expand Down
6 changes: 2 additions & 4 deletions src/mpol/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ def flat_to_observer(x, y, omega=None, incl=None, Omega=None):
Returns:
Two tensors representing ``(X, Y)`` in the observer frame.
"""

# Rotation matrices result in a *clockwise* rotation of the axes, as defined using the righthand rule.
# For example, looking down the z-axis, a positive angle will rotate the x,y axes clockwise.
# A vector in the coordinate system will appear as though it has been rotated counter-clockwise.
Expand Down Expand Up @@ -70,7 +69,7 @@ def flat_to_observer(x, y, omega=None, incl=None, Omega=None):


def observer_to_flat(X, Y, omega=None, incl=None, Omega=None):
"""Rotate the from to convert a point in the observer frame (X,Y,Z) to the flat (x,y,z) frame.
"""Rotate the frame to convert a point in the observer frame (X,Y,Z) to the flat (x,y,z) frame.

It is assumed that the +Z axis points *towards* the observer. The rotation operations are the inverse of the :func:`~mpol.geometry.flat_to_observer` operations.

Expand All @@ -90,9 +89,8 @@ def observer_to_flat(X, Y, omega=None, incl=None, Omega=None):
Omega (torch float tensor): A tensor representing the position angle of the ascending node in [radians]. Default 0.0

Returns:
Two tensors representing ``(x, y)`` in the observer frame.
Two tensors representing ``(x, y)`` in the flat frame.
"""

# Rotation matrices result in a *clockwise* rotation of the axes, as defined using the righthand rule.
# For example, looking down the z-axis, a positive angle will rotate the x,y axes clockwise.
# A vector in the coordinate system will appear as though it has been rotated counter-clockwise.
Expand Down
2 changes: 1 addition & 1 deletion src/mpol/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,4 +315,4 @@ def to_FITS(self, fname="cube.fits", overwrite=False, header_kwargs=None):
hdul = fits.HDUList([hdu])
hdul.writeto(fname, overwrite=overwrite)

hdul.close()
hdul.close()
170 changes: 170 additions & 0 deletions src/mpol/onedim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import numpy as np
from mpol.utils import torch2npy

def radialI(icube, geom, chan=0, bins=None):
r"""
Obtain a 1D (radial) brightness profile I(r) from an image cube.

Parameters
----------
icube : `mpol.images.ImageCube` object
Instance of the MPoL `images.ImageCube` class
geom : dict
Dictionary of source geometry. Keys:
"incl" : float, unit=[deg]
Inclination
"Omega" : float, unit=[deg]
Position angle of the ascending node
"omega" : float, unit=[deg]
Argument of periastron
"dRA" : float, unit=[arcsec]
Phase center offset in right ascension. Positive is west of north.
"dDec" : float, unit=[arcsec]
Phase center offset in declination.
chan : int, default=0
Channel of the image cube corresponding to the desired image
bins : array, default=None, unit=[arcsec]
Radial bin edges to use in calculating I(r). If None, bins will span
the full image, with widths equal to the hypotenuse of the pixels

Returns
-------
bin_centers : array, unit=[arcsec]
Radial coordinates of image at center of `bins`
Is : array, unit=[Jy / arcsec^2] (if `image` has these units)
Azimuthally averaged pixel brightness at `rs`
"""

# projected Cartesian pixel coordinates [arcsec]
xx, yy = icube.coords.sky_x_centers_2D, icube.coords.sky_y_centers_2D

# shift image center to source center
xc, yc = xx - geom["dRA"], yy - geom["dDec"]

# deproject image
cos_PA = np.cos(geom["Omega"] * np.pi / 180)
sin_PA = np.sin(geom["Omega"] * np.pi / 180)
xd = xc * cos_PA - yc * sin_PA
yd = xc * sin_PA + yc * cos_PA
xd /= np.cos(geom["incl"] * np.pi / 180)

# deprojected radial coordinates
rr = np.ravel(np.hypot(xd, yd))

if bins is None:
# choose sensible bin size and range
step = np.hypot(icube.coords.cell_size, icube.coords.cell_size)
bins = np.arange(0.0, np.max((abs(xc.ravel()), abs(yc.ravel()))), step)

bin_counts, bin_edges = np.histogram(a=rr, bins=bins, weights=None)

# cumulative binned brightness in each annulus
Is, _ = np.histogram(a=rr, bins=bins,
weights=torch2npy(icube.sky_cube[chan]).ravel()
)

# mask empty bins
mask = (bin_counts == 0)
Is = np.ma.masked_where(mask, Is)

# average binned brightness in each annulus
Is /= bin_counts

bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2

return bin_centers, Is


def radialV(fcube, geom, rescale_flux, chan=0, bins=None):
r"""
Obtain the 1D (radial) visibility model V(q) corresponding to a 2D MPoL
image.

Parameters
----------
fcube : `~mpol.fourier.FourierCube` object
Instance of the MPoL `fourier.FourierCube` class
geom : dict
Dictionary of source geometry. Keys:
"incl" : float, unit=[deg]
Inclination
"Omega" : float, unit=[deg]
Position angle of the ascending node
"omega" : float, unit=[deg]
Argument of periastron
"dRA" : float, unit=[arcsec]
Phase center offset in right ascension. Positive is west of north.
"dDec" : float, unit=[arcsec]
Phase center offset in declination
rescale_flux : bool
If True, the visibility amplitudes and weights are rescaled to account
for the difference between the inclined (observed) brightness and the
assumed face-on brightness, assuming the emission is optically thick.
The source's integrated (2D) flux is assumed to be:
:math:`F = \cos(i) \int_r^{r=R}{I(r) 2 \pi r dr}`.
No rescaling would be appropriate in the optically thin limit.
chan : int, default=0
Channel of the image cube corresponding to the desired image
bins : array, default=None, unit=[k\lambda]
Baseline bin edges to use in calculating V(q). If None, bins will span
the model baseline distribution, with widths equal to the hypotenuse of
the (u, v) coordinates

Returns
-------
bin_centers : array, unit=:math:[`k\lambda`]
Baselines corresponding to `u` and `v`
Vs : array, unit=[Jy]
Visibility amplitudes at `q`

Notes
-----
This routine requires the `frank <https://github.com/discsim/frank>`_ package
"""
from frank.geometry import apply_phase_shift, deproject

# projected model (u,v) points [k\lambda]
uu, vv = fcube.coords.sky_u_centers_2D, fcube.coords.sky_v_centers_2D

# visibilities
V = torch2npy(fcube.ground_cube[chan]).ravel()

# phase-shift the visibilities
Vp = apply_phase_shift(uu.ravel() * 1e3, vv.ravel() * 1e3, V, geom["dRA"],
geom["dDec"], inverse=True)

# deproject the (u,v) points
up, vp, _ = deproject(uu.ravel() * 1e3, vv.ravel() * 1e3, geom["incl"],
geom["Omega"])

# if the source is optically thick, rescale the deprojected V(q)
if rescale_flux:
Vp.real /= np.cos(geom["incl"] * np.pi / 180)

# convert back to [k\lambda]
up /= 1e3
vp /= 1e3

# deprojected baselines
qq = np.hypot(up, vp)

if bins is None:
# choose sensible bin size and range
step = np.hypot(fcube.coords.du, fcube.coords.dv) / 2
bins = np.arange(0.0, max(qq), step)

bin_counts, bin_edges = np.histogram(a=qq, bins=bins, weights=None)

# cumulative binned visibility amplitude in each annulus
Vs, _ = np.histogram(a=qq, bins=bins, weights=Vp)

# mask empty bins
mask = (bin_counts == 0)
Vs = np.ma.masked_where(mask, Vs)

# average binned visibility amplitude in each annulus
Vs /= bin_counts

bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2

return bin_centers, Vs
1 change: 0 additions & 1 deletion src/mpol/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,6 @@ def get_optimal_image_properties(image_width, u, v):
npix : int
Number of pixels of cell_size to equal (or slightly exceed) the image
width (npix will be rounded up and enforced as even).

Notes
-----
No assumption or correction is made concerning whether the spatial
Expand Down
68 changes: 67 additions & 1 deletion test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import numpy as np
import pytest
import torch
from astropy.utils.data import download_file

from mpol import coordinates, gridding
from mpol import coordinates, fourier, gridding, images
from mpol.__init__ import zenodo_record

# We need a fixture which provides mock visibilities of the sort we'd
Expand Down Expand Up @@ -116,6 +117,71 @@ def dataset_cont(mock_visibility_data_cont, coords):
return averager.to_pytorch_dataset()


@pytest.fixture(scope="session")
def mock_1d_archive():
# use astropy routines to cache data
fname = download_file(
f"https://zenodo.org/record/{zenodo_record}/files/mock_disk_1d.npz",
cache=True,
pkgname="mpol",
)

return np.load(fname, allow_pickle=True)


@pytest.fixture
def mock_1d_image_model(mock_1d_archive):
m = mock_1d_archive
rtrue = m['rtrue']
itrue = m['itrue']
i2dtrue = m['i2dtrue']
xmax = ymax = m['xmax']
geom = m['geometry']
geom = geom[()]

coords = coordinates.GridCoords(cell_size=xmax * 2 / i2dtrue.shape[0],
npix=i2dtrue.shape[0])

# the center of the array is already at the center of the image -->
# undo this as expected by input to ImageCube
i2dtrue = np.flip(np.fft.fftshift(i2dtrue), 1)

# pack the numpy image array into an ImageCube
packed_cube = np.broadcast_to(i2dtrue, (1, coords.npix, coords.npix)).copy()
packed_tensor = torch.from_numpy(packed_cube)
cube_true = images.ImageCube(coords=coords, nchan=1, cube=packed_tensor)

return rtrue, itrue, cube_true, xmax, ymax, geom


@pytest.fixture
def mock_1d_vis_model(mock_1d_archive):
m = mock_1d_archive
Vtrue = m['vis']
Vtrue_dep = m['vis_dep']
q_dep = m['baselines_dep']
geom = m['geometry']
geom = geom[()]

xmax = m['xmax']
i2dtrue = m['i2dtrue']

coords = coordinates.GridCoords(cell_size=xmax * 2 / i2dtrue.shape[0],
npix=i2dtrue.shape[0])

# create a FourierCube
packed_cube = np.broadcast_to(Vtrue, (1, len(Vtrue))).copy()
packed_tensor = torch.from_numpy(packed_cube)
cube_true = fourier.FourierCube(coords=coords)

# insert the vis tensor into the FourierCube ('vis' would typically be
# populated by taking the FFT of an image)
cube_true.ground_cube = packed_tensor

return cube_true, Vtrue_dep, q_dep, geom



@pytest.fixture
def crossvalidation_products(mock_visibility_data):
# test the crossvalidation with a smaller set of image / Fourier coordinates than normal,
Expand Down
Loading