From 20b91b97533a1315cd343ecbcaf5fd5e62987f7a Mon Sep 17 00:00:00 2001 From: Ian Czekala Date: Mon, 25 May 2026 15:22:07 +1000 Subject: [PATCH 1/3] Added __version__ variable back in for wheels packages. --- src/mpol/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mpol/__init__.py b/src/mpol/__init__.py index 5d74b25a..f936668c 100644 --- a/src/mpol/__init__.py +++ b/src/mpol/__init__.py @@ -1 +1,8 @@ zenodo_record = 10064221 + +import importlib.metadata + +try: + __version__ = importlib.metadata.version("MPoL") +except importlib.metadata.PackageNotFoundError: + __version__ = "unknown" \ No newline at end of file From 79d7794318562eba39f5f11ba088e6612a63fb9d Mon Sep 17 00:00:00 2001 From: Ian Czekala Date: Mon, 25 May 2026 15:28:56 +1000 Subject: [PATCH 2/3] ruff formatted. --- docs/conf.py | 1 - src/mpol/__init__.py | 2 +- src/mpol/coordinates.py | 4 +- src/mpol/crossval.py | 6 +- src/mpol/datasets.py | 14 +- src/mpol/exceptions.py | 15 +- src/mpol/fourier.py | 9 +- src/mpol/geometry.py | 3 +- src/mpol/images.py | 42 +++--- src/mpol/input_output.py | 9 +- src/mpol/losses.py | 60 ++++---- src/mpol/onedim.py | 4 +- src/mpol/plot.py | 12 +- src/mpol/utils.py | 6 +- test/conftest.py | 3 + test/coordinates_test.py | 5 +- test/datasets_test.py | 9 +- test/fftshift_test.py | 7 +- test/fourier_test.py | 62 ++++---- test/geometry_test.py | 30 ++-- test/gridder_imager_test.py | 2 +- test/gridder_init_test.py | 8 +- test/input_output_test.py | 1 - test/losses_test.py | 5 +- test/onedim_test.py | 276 ++++++++++++++++++++++++++++-------- test/plot_test.py | 2 - test/plot_utils.py | 49 ++++--- test/utils_test.py | 4 +- 28 files changed, 398 insertions(+), 252 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9666fee1..5a3d360e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ - # -- Project information ----------------------------------------------------- from pkg_resources import DistributionNotFound, get_distribution diff --git a/src/mpol/__init__.py b/src/mpol/__init__.py index f936668c..c5963cf1 100644 --- a/src/mpol/__init__.py +++ b/src/mpol/__init__.py @@ -5,4 +5,4 @@ try: __version__ = importlib.metadata.version("MPoL") except importlib.metadata.PackageNotFoundError: - __version__ = "unknown" \ No newline at end of file + __version__ = "unknown" diff --git a/src/mpol/coordinates.py b/src/mpol/coordinates.py index a84d599f..806fe23c 100644 --- a/src/mpol/coordinates.py +++ b/src/mpol/coordinates.py @@ -104,7 +104,7 @@ class GridCoords: length-4 list of (left, right, bottom, top) expected by routines like ``matplotlib.pyplot.imshow`` in the ``extent`` parameter assuming ``origin='lower'``. Units of [:math:`\lambda`] - vis_ext_Mlam : list + vis_ext_Mlam : list like vis_ext, but in units of [:math:`\mathrm{M}\lambda`]. """ @@ -273,7 +273,7 @@ def ground_q_centers_2D(self) -> npt.NDArray[np.floating[Any]]: @cached_property def sky_phi_centers_2D(self) -> npt.NDArray[np.floating[Any]]: # https://en.wikipedia.org/wiki/Atan2 - return np.arctan2( # type: ignore + return np.arctan2( # type: ignore self.ground_v_centers_2D, self.ground_u_centers_2D ) # (pi, pi] diff --git a/src/mpol/crossval.py b/src/mpol/crossval.py index cb90ac03..8fcc98f0 100644 --- a/src/mpol/crossval.py +++ b/src/mpol/crossval.py @@ -56,7 +56,7 @@ # Number of k-folds to use in cross-validation # split_method : str, default='dartboard' # Method to split full dataset into train/test subsets -# dartboard_q_edges, dartboard_phi_edges : list of float, default=None, +# dartboard_q_edges, dartboard_phi_edges : list of float, default=None, # unit=[klambda] # Radial and azimuthal bin edges of the cells used to split the dataset # if `split_method`==`dartboard` (see `datasets.Dartboard`) @@ -510,7 +510,7 @@ def __init__( self.cell_list = self.dartboard.get_nonzero_cell_indices(qs, phis) # indices of cells in the smallest q bin that also have data - small_q_idx = [i for i, l in enumerate(self.cell_list) if l[0] == 0] #type: ignore + small_q_idx = [i for i, l in enumerate(self.cell_list) if l[0] == 0] # type: ignore # cells in the smallest q bin self.small_q = self.cell_list[: len(small_q_idx)] @@ -554,7 +554,7 @@ def from_dartboard_properties( seed (int): (optional) numpy random seed to use for the permutation, for reproducibility verbose (bool): whether to print notification messages - """ + """ dartboard = Dartboard(gridded_dataset.coords, q_edges, phi_edges) return cls(gridded_dataset, k, dartboard, seed, verbose) diff --git a/src/mpol/datasets.py b/src/mpol/datasets.py index 510db7c4..a2d20637 100644 --- a/src/mpol/datasets.py +++ b/src/mpol/datasets.py @@ -19,7 +19,7 @@ class GriddedDataset(torch.nn.Module): If providing this, cannot provide ``cell_size`` or ``npix``. vis_gridded : :class:`torch.Tensor` of :class:`torch.complex128` the gridded visibility data stored in a "packed" format (pre-shifted for fft) - weight_gridded : :class:`torch.Tensor` + weight_gridded : :class:`torch.Tensor` the weights corresponding to the gridded visibility data, also in a packed format mask : :class:`torch.Tensor` of :class:`torch.bool` @@ -117,9 +117,9 @@ def forward(self, modelVisibilityCube: torch.Tensor) -> torch.Tensor: across cube dimensions. """ - assert ( - modelVisibilityCube.size()[0] == self.mask.size()[0] - ), "vis and dataset mask do not have the same number of channels." + assert modelVisibilityCube.size()[0] == self.mask.size()[0], ( + "vis and dataset mask do not have the same number of channels." + ) # As of Pytorch 1.7.0, complex numbers are partially supported. # However, masked_select does not yet work (with gradients) @@ -226,8 +226,10 @@ def get_polar_histogram( histogram: NDArray # make a polar histogram - histogram, *_ = np.histogram2d( # type:ignore - qs, phis, bins=[self.q_edges.tolist(), self.phi_edges.tolist()] # type:ignore + histogram, *_ = np.histogram2d( # type:ignore + qs, + phis, + bins=[self.q_edges.tolist(), self.phi_edges.tolist()], # type:ignore ) return histogram diff --git a/src/mpol/exceptions.py b/src/mpol/exceptions.py index f74ab860..0c2f815a 100644 --- a/src/mpol/exceptions.py +++ b/src/mpol/exceptions.py @@ -1,21 +1,16 @@ from __future__ import annotations -class CellSizeError(Exception): - ... +class CellSizeError(Exception): ... -class WrongDimensionError(Exception): - ... +class WrongDimensionError(Exception): ... -class DataError(Exception): - ... +class DataError(Exception): ... -class DimensionMismatchError(Exception): - ... +class DimensionMismatchError(Exception): ... -class ThresholdExceededError(Exception): - ... +class ThresholdExceededError(Exception): ... diff --git a/src/mpol/fourier.py b/src/mpol/fourier.py index 82e63795..50f6c478 100644 --- a/src/mpol/fourier.py +++ b/src/mpol/fourier.py @@ -275,14 +275,14 @@ def forward( Parameters ---------- - packed_cube : :class:`torch.Tensor` + packed_cube : :class:`torch.Tensor` shape ``(nchan, npix, npix)``). The cube should be a "prepacked" image cube, for example, from :meth:`mpol.images.ImageCube.forward` - uu : :class:`torch.Tensor` + uu : :class:`torch.Tensor` 2D array of the u (East-West) spatial frequency coordinate [:math:`\lambda`] of shape ``(nchan, npix)`` - vv : :class:`torch.Tensor` + vv : :class:`torch.Tensor` 2D array of the v (North-South) spatial frequency coordinate [:math:`\lambda`] (must be the same shape as uu) sparse_matrices : bool @@ -506,7 +506,7 @@ def forward(self, packed_cube): ``uu`` and ``vv`` points. Args: - packed_cube : :class:`torch.Tensor` + packed_cube : :class:`torch.Tensor` shape ``(nchan, npix, npix)``). The cube should be a "prepacked" image cube, for example, from :meth:`mpol.images.ImageCube.forward` @@ -530,7 +530,6 @@ def forward(self, packed_cube): # convert the cube to a complex type, since this is required by TorchKbNufft complexed = shifted + 0j - # Consider how the similarity of the spatial frequency samples should be # treated. We already took care of this on the k_traj side, since we set # the shapes. But this also needs to be taken care of on the image side. diff --git a/src/mpol/geometry.py b/src/mpol/geometry.py index 98d41013..44d3d240 100644 --- a/src/mpol/geometry.py +++ b/src/mpol/geometry.py @@ -1,5 +1,4 @@ -"""The geometry package provides routines for projecting and de-projecting sky images. -""" +"""The geometry package provides routines for projecting and de-projecting sky images.""" import numpy as np import torch diff --git a/src/mpol/images.py b/src/mpol/images.py index c6d48bf9..068d2da0 100644 --- a/src/mpol/images.py +++ b/src/mpol/images.py @@ -83,9 +83,9 @@ def __init__( self.base_cube = nn.Parameter(base_cube, requires_grad=True) if pixel_mapping is None: - self.pixel_mapping: Callable[ - [torch.Tensor], torch.Tensor - ] = torch.nn.Softplus() + self.pixel_mapping: Callable[[torch.Tensor], torch.Tensor] = ( + torch.nn.Softplus() + ) else: self.pixel_mapping = pixel_mapping @@ -193,10 +193,10 @@ def forward(self, cube: torch.Tensor) -> torch.Tensor: class GaussConvImage(nn.Module): r""" - This convolutional layer will convolve the input cube with a 2D Gaussian kernel. + This convolutional layer will convolve the input cube with a 2D Gaussian kernel. The filter is the same for all channels in the input cube. Because the operation is carried out in the image domain, note that it may become - computationally prohibitive for large kernel sizes. In that case, + computationally prohibitive for large kernel sizes. In that case, :class:`mpol.images.GaussConvFourier` may be preferred. Parameters @@ -329,6 +329,7 @@ def forward(self, sky_cube: torch.Tensor) -> torch.Tensor: convolved_sky = self.m(sky_cube) return convolved_sky + class GaussConvFourier(nn.Module): r""" This layer will convolve the input cube with a (potentially non-circular) Gaussian @@ -349,19 +350,18 @@ class GaussConvFourier(nn.Module): """ def __init__( - self, - coords: GridCoords, - FWHM_maj: float, - FWHM_min: float, - Omega: float = 0) -> None: + self, coords: GridCoords, FWHM_maj: float, FWHM_min: float, Omega: float = 0 + ) -> None: super().__init__() self.coords = coords self.FWHM_maj = FWHM_maj - self.FWHM_min = FWHM_min + self.FWHM_min = FWHM_min self.Omega = Omega - taper_2D = uv_gaussian_taper(self.coords, self.FWHM_maj, self.FWHM_min, self.Omega) + taper_2D = uv_gaussian_taper( + self.coords, self.FWHM_maj, self.FWHM_min, self.Omega + ) # store taper to register so it transfers to GPU self.register_buffer("taper_2D", torch.tensor(taper_2D, dtype=torch.float32)) @@ -382,10 +382,10 @@ def forward(self, packed_cube, thresh=1e-6): The convolved cube in packed format. """ nchan, npix_m, npix_l = packed_cube.size() - assert ( - (npix_m == self.coords.npix) and (npix_l == self.coords.npix) - ), "packed_cube {:} does not have the same pixel dimensions as indicated by coords {:}".format( - packed_cube.size(), self.coords.npix + assert (npix_m == self.coords.npix) and (npix_l == self.coords.npix), ( + "packed_cube {:} does not have the same pixel dimensions as indicated by coords {:}".format( + packed_cube.size(), self.coords.npix + ) ) # in FFT packed format @@ -400,10 +400,10 @@ def forward(self, packed_cube, thresh=1e-6): convolved_packed_cube = torch.fft.ifftn(tapered_vis, dim=(1, 2)) # assert imaginaries are effectively zero, otherwise something went wrong - assert ( - torch.max(convolved_packed_cube.imag) < thresh - ), "Round-tripped image contains max imaginary value {:} > {:} threshold, something may be amiss.".format( - torch.max(convolved_packed_cube.imag), thresh + assert torch.max(convolved_packed_cube.imag) < thresh, ( + "Round-tripped image contains max imaginary value {:} > {:} threshold, something may be amiss.".format( + torch.max(convolved_packed_cube.imag), thresh + ) ) r_cube: torch.Tensor = convolved_packed_cube.real @@ -580,4 +580,4 @@ def uv_gaussian_taper( # a flux-conserving taper must have an amplitude of 1 at the origin. - return taper_2D \ No newline at end of file + return taper_2D diff --git a/src/mpol/input_output.py b/src/mpol/input_output.py index 1655773c..4a4afb18 100644 --- a/src/mpol/input_output.py +++ b/src/mpol/input_output.py @@ -18,7 +18,6 @@ def __init__(self, filename, channel=0): self._fits_file = filename self._channel = channel - def get_extent(self, header): """Get extent (in RA and Dec, units of [arcsec]) of image""" @@ -26,9 +25,9 @@ def get_extent(self, header): nx = header["NAXIS1"] ny = header["NAXIS2"] - assert ( - nx % 2 == 0 and ny % 2 == 0 - ), f"Image dimensions x {nx} and y {ny} must be even." + assert nx % 2 == 0 and ny % 2 == 0, ( + f"Image dimensions x {nx} and y {ny} must be even." + ) # RA coordinates CDELT1 = 3600 * header["CDELT1"] # arcsec (converted from decimal deg) @@ -53,7 +52,6 @@ def get_extent(self, header): return RA, DEC, ext - def get_beam(self, hdu_list, header): """Get the major and minor widths [arcsec], and position angle, of a clean beam""" @@ -72,7 +70,6 @@ def get_beam(self, hdu_list, header): return BMAJ, BMIN, BPA - def get_image(self, beam=True): """Load a .fits image and return as a numpy array. Also return image extent and optionally (`beam`) the clean beam dimensions""" diff --git a/src/mpol/losses.py b/src/mpol/losses.py index 00e4c1e6..bd7af8bf 100644 --- a/src/mpol/losses.py +++ b/src/mpol/losses.py @@ -30,12 +30,12 @@ def _chi_squared( array of the model values representing :math:`\boldsymbol{V}` data_vis : :class:`torch.Tensor` of :class:`torch.complex` array of the data values representing :math:`M` - weight : :class:`torch.Tensor` + weight : :class:`torch.Tensor` array of weight values representing :math:`w_i` Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` the :math:`\chi^2` likelihood, summed over all dimensions of input array. """ @@ -79,12 +79,12 @@ def r_chi_squared( array of the model values representing :math:`\boldsymbol{V}` data_vis : :class:`torch.Tensor` of :class:`torch.complex` array of the data values representing :math:`M` - weight : :class:`torch.Tensor` + weight : :class:`torch.Tensor` array of weight values representing :math:`w_i` Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` the :math:`\chi^2_\mathrm{R}`, summed over all dimensions of input array. """ @@ -116,7 +116,7 @@ def r_chi_squared_gridded( Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` the :math:`\chi^2_\mathrm{R}` value summed over all input dimensions """ model_vis = griddedDataset(modelVisibilityCube) @@ -166,12 +166,12 @@ def log_likelihood( array of the model values representing :math:`\boldsymbol{V}` data_vis : :class:`torch.Tensor` of :class:`torch.complex128` array of the data values representing :math:`M` - weight : :class:`torch.Tensor` + weight : :class:`torch.Tensor` array of weight values representing :math:`w_i` Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` the :math:`\ln\mathcal{L}` log likelihood, summed over all dimensions of input array. """ @@ -208,7 +208,7 @@ def log_likelihood_gridded( Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` the :math:`\ln\mathcal{L}` value, summed over all dimensions of input data. """ @@ -247,12 +247,12 @@ def neg_log_likelihood_avg( array of the model values representing :math:`\boldsymbol{V}` data_vis : :class:`torch.Tensor` of :class:`torch.complex` array of the data values representing :math:`M` - weight : :class:`torch.Tensor` + weight : :class:`torch.Tensor` array of weight values representing :math:`w_i` Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` the average of the negative log likelihood, summed over all dimensions of input array. """ @@ -275,9 +275,9 @@ def entropy( Parameters ---------- - cube : :class:`torch.Tensor` + cube : :class:`torch.Tensor` pixel values must be positive :math:`I_i > 0` for all :math:`i` - prior_intensity : :class:`torch.Tensor` + prior_intensity : :class:`torch.Tensor` the prior value :math:`p` to calculate entropy against. Tensors of any shape are allowed so long as they will broadcast to the shape of the cube under division (`/`). @@ -287,7 +287,7 @@ def entropy( Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` entropy loss """ # check to make sure image is positive, otherwise raise an error @@ -313,7 +313,7 @@ def TV_image(sky_cube: torch.Tensor, epsilon: float = 1e-10) -> torch.Tensor: Parameters ---------- - sky_cube: 3D :class:`torch.Tensor` + sky_cube: 3D :class:`torch.Tensor` the image cube array :math:`I_{lmv}`, where :math:`l` is R.A. in :math:`ndim=3`, :math:`m` is DEC in :math:`ndim=2`, and :math:`v` is the channel (velocity or frequency) dimension in @@ -325,7 +325,7 @@ def TV_image(sky_cube: torch.Tensor, epsilon: float = 1e-10) -> torch.Tensor: Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` total variation loss """ @@ -348,7 +348,7 @@ def TV_channel(cube: torch.Tensor, epsilon: float = 1e-10) -> torch.Tensor: Parameters ---------- - cube: :class:`torch.Tensor` + cube: :class:`torch.Tensor` the image cube array :math:`I_{lmv}` epsilon: float a softening parameter in units of [:math:`\mathrm{Jy}/\mathrm{arcsec}^2`]. @@ -357,7 +357,7 @@ def TV_channel(cube: torch.Tensor, epsilon: float = 1e-10) -> torch.Tensor: Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` total variation loss """ # calculate the difference between the n+1 cube and the n cube @@ -383,7 +383,7 @@ def TSV(sky_cube: torch.Tensor) -> torch.Tensor: Parameters ---------- - sky_cube :class:`torch.Tensor` + sky_cube :class:`torch.Tensor` the image cube array :math:`I_{lmv}`, where :math:`l` is R.A. in :math:`ndim=3`, :math:`m` is DEC in :math:`ndim=2`, and :math:`v` is the channel (velocity or frequency) dimension in @@ -391,7 +391,7 @@ def TSV(sky_cube: torch.Tensor) -> torch.Tensor: Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` total square variation loss """ @@ -418,7 +418,7 @@ def sparsity(cube: torch.Tensor, mask: Optional[torch.Tensor] = None) -> torch.T Parameters ---------- - cube : :class:`torch.Tensor` + cube : :class:`torch.Tensor` the image cube array :math:`I_{lmv}` mask : :class:`torch.Tensor` of :class:`torch.bool` tensor array the same shape as ``cube``. The sparsity prior @@ -427,7 +427,7 @@ def sparsity(cube: torch.Tensor, mask: Optional[torch.Tensor] = None) -> torch.T Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` sparsity loss calculated where ``mask == True`` """ @@ -458,7 +458,7 @@ def UV_sparsity( Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` UV sparsity loss above :math:`q_\mathrm{max}` """ @@ -498,16 +498,16 @@ def PSD(qs: torch.Tensor, psd: torch.Tensor, l: torch.Tensor) -> torch.Tensor: Parameters ---------- - qs : :class:`torch.Tensor` + qs : :class:`torch.Tensor` the radial UV coordinate (in :math:`\lambda`) - psd : :class:`torch.Tensor` + psd : :class:`torch.Tensor` the power spectral density cube - l : :class:`torch.Tensor` + l : :class:`torch.Tensor` the correlation length in the image plane (in arcsec) Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` the loss calculated using the power spectral density """ @@ -515,9 +515,7 @@ def PSD(qs: torch.Tensor, psd: torch.Tensor, l: torch.Tensor) -> torch.Tensor: l_rad = l * constants.arcsec # radians # calculate the expected power spectral density - expected_PSD = ( - 2 * np.pi * l_rad**2 * torch.exp(-2 * np.pi**2 * l_rad**2 * qs**2) - ) + expected_PSD = 2 * np.pi * l_rad**2 * torch.exp(-2 * np.pi**2 * l_rad**2 * qs**2) # evaluate the chi^2 for the PSD, making sure it broadcasts across all channels loss = torch.sum(psd / expected_PSD) @@ -531,12 +529,12 @@ def edge_clamp(cube: torch.Tensor) -> torch.Tensor: Parameters ---------- - cube: :class:`torch.Tensor` + cube: :class:`torch.Tensor` the image cube array :math:`I_{lmv}` Returns ------- - :class:`torch.Tensor` + :class:`torch.Tensor` edge loss """ diff --git a/src/mpol/onedim.py b/src/mpol/onedim.py index 408edae8..30c515af 100644 --- a/src/mpol/onedim.py +++ b/src/mpol/onedim.py @@ -137,9 +137,7 @@ def radialV(fcube, geom, rescale_flux, chan=0, bins=None): ) # deproject the (u,v) points - up, vp, _ = deproject( - uu.ravel(), vv.ravel(), geom["incl"], geom["Omega"] - ) + up, vp, _ = deproject(uu.ravel(), vv.ravel(), geom["incl"], geom["Omega"]) # if the source is optically thick, rescale the deprojected V(q) if rescale_flux: diff --git a/src/mpol/plot.py b/src/mpol/plot.py index afdf0872..40992d1e 100644 --- a/src/mpol/plot.py +++ b/src/mpol/plot.py @@ -874,12 +874,8 @@ def vis_1d_fig( if geom is not None: # phase-shift the visibilities - V = apply_phase_shift( - uu, vv, V, geom["dRA"], geom["dDec"], inverse=True - ) - Vmod = apply_phase_shift( - uu, vv, Vmod, geom["dRA"], geom["dDec"], inverse=True - ) + V = apply_phase_shift(uu, vv, V, geom["dRA"], geom["dDec"], inverse=True) + Vmod = apply_phase_shift(uu, vv, Vmod, geom["dRA"], geom["dDec"], inverse=True) Vresid = apply_phase_shift( uu, vv, Vresid, geom["dRA"], geom["dDec"], inverse=True ) @@ -1072,9 +1068,7 @@ def radial_fig( from frank.utilities import UVDataBinner # phase-shift the observed visibilities - V = apply_phase_shift( - u, v, V, geom["dRA"], geom["dDec"], inverse=True - ) + V = apply_phase_shift(u, v, V, geom["dRA"], geom["dDec"], inverse=True) # deproject the observed (u,v) points u, v, _ = deproject(u, v, geom["incl"], geom["Omega"]) diff --git a/src/mpol/utils.py b/src/mpol/utils.py index 9173ff3b..bafb9ce6 100644 --- a/src/mpol/utils.py +++ b/src/mpol/utils.py @@ -324,9 +324,9 @@ def get_optimal_image_properties( npix += 1 # should never occur - assert ( - get_max_spatial_freq(cell_size, npix) >= max_freq - ), "error in get_optimal_image_properties" + assert get_max_spatial_freq(cell_size, npix) >= max_freq, ( + "error in get_optimal_image_properties" + ) return cell_size, npix diff --git a/test/conftest.py b/test/conftest.py index 24dd326b..8a4912b2 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -9,6 +9,7 @@ from mpol.__init__ import zenodo_record import matplotlib.pyplot as plt + plt.style.use("mpol.tests") # private variables to this module @@ -31,6 +32,7 @@ def img2D_butterfly(): return img + @pytest.fixture(scope="session") def sky_cube(img2D_butterfly): """Create a sky tensor image cube from the butterfly.""" @@ -39,6 +41,7 @@ def sky_cube(img2D_butterfly): sky_cube = torch.tile(torch.from_numpy(img2D_butterfly), (_nchan, 1, 1)) return sky_cube + @pytest.fixture(scope="session") def packed_cube(img2D_butterfly): """Create a packed tensor image cube from the butterfly.""" diff --git a/test/coordinates_test.py b/test/coordinates_test.py index 1b22b351..38be4003 100644 --- a/test/coordinates_test.py +++ b/test/coordinates_test.py @@ -116,7 +116,9 @@ def test_tile_vs_meshgrid_implementation(): coords = coordinates.GridCoords(cell_size=0.05, npix=800) x_centers_2d, y_centers_2d = np.meshgrid( - coords.l_centers / constants.arcsec, coords.m_centers / constants.arcsec, indexing="xy" + coords.l_centers / constants.arcsec, + coords.m_centers / constants.arcsec, + indexing="xy", ) ground_u_centers_2D, ground_v_centers_2D = np.meshgrid( @@ -128,6 +130,7 @@ def test_tile_vs_meshgrid_implementation(): assert np.all(coords.x_centers_2D == x_centers_2d) assert np.all(coords.y_centers_2D == y_centers_2d) + def test_coords_mock_image(coords, img2D_butterfly): npix, _ = img2D_butterfly.shape assert coords.npix == npix, "coords dimensions and mock image have different sizes" diff --git a/test/datasets_test.py b/test/datasets_test.py index 34923ed5..7e2b9e18 100644 --- a/test/datasets_test.py +++ b/test/datasets_test.py @@ -14,10 +14,8 @@ def test_index(coords, dataset): # create a mock cube that includes negative values nchan = dataset.nchan - mean = torch.full( - (nchan, coords.npix, coords.npix), fill_value=-0.5) - std = torch.full( - (nchan, coords.npix, coords.npix), fill_value=0.5) + mean = torch.full((nchan, coords.npix, coords.npix), fill_value=-0.5) + std = torch.full((nchan, coords.npix, coords.npix), fill_value=0.5) # tensor base_cube = torch.normal(mean=mean, std=std) @@ -77,7 +75,6 @@ def test_dartboard_init(coords): def test_dartboard_histogram(coords, dataset, tmp_path): - # use default bins dartboard = datasets.Dartboard(coords=coords) @@ -119,7 +116,6 @@ def test_dartboard_histogram(coords, dataset, tmp_path): def test_dartboard_nonzero(coords, dataset, tmp_path): - # use default bins dartboard = datasets.Dartboard(coords=coords) @@ -173,7 +169,6 @@ def test_dartboard_mask(coords, dataset, tmp_path): def test_hermitian_mask_full(coords, dataset, tmp_path): - dartboard = datasets.Dartboard(coords=coords) chan = 1 diff --git a/test/fftshift_test.py b/test/fftshift_test.py index 52ccca6b..55d897d0 100644 --- a/test/fftshift_test.py +++ b/test/fftshift_test.py @@ -4,7 +4,6 @@ def test_mpol_fftshift(tmp_path): - # create a fake image xx, yy = np.mgrid[0:20, 0:20] image_init = xx + 2 * yy @@ -24,6 +23,6 @@ def test_mpol_fftshift(tmp_path): ax[2].imshow(shifted_numpy - shifted_torch.detach().numpy(), origin="upper") fig.savefig(str(tmp_path / "fftshift.png")) - assert np.allclose( - shifted_numpy, shifted_torch.detach().numpy() - ), "fftshifts do not match" + assert np.allclose(shifted_numpy, shifted_torch.detach().numpy()), ( + "fftshifts do not match" + ) diff --git a/test/fourier_test.py b/test/fourier_test.py index 8acbf501..2d90dc65 100644 --- a/test/fourier_test.py +++ b/test/fourier_test.py @@ -6,20 +6,20 @@ # parameters for analytic Gaussian used for all tests gauss_kw = { - "a": 1, - "delta_x": 0.02, # arcsec - "delta_y": -0.01, - "sigma_x": 0.02, - "sigma_y": 0.01, - "Omega": 20, # degrees - } + "a": 1, + "delta_x": 0.02, # arcsec + "delta_y": -0.01, + "sigma_x": 0.02, + "sigma_y": 0.01, + "Omega": 20, # degrees +} + def test_fourier_cube(coords, tmp_path): # test image packing # test whether we get the same Fourier Transform using the FFT as we could # calculate analytically - img_packed = utils.sky_gaussian_arcsec( coords.packed_x_centers_2D, coords.packed_y_centers_2D, **gauss_kw ) @@ -165,16 +165,16 @@ def test_predict_vis_nufft_cached(coords, baselines_1D): layer = fourier.NuFFTCached(coords=coords, nchan=nchan, uu=uu, vv=vv) # predict the values of the cube at the u,v locations - blank_packed_img = torch.zeros((nchan, coords.npix, coords.npix), dtype=torch.double) + blank_packed_img = torch.zeros( + (nchan, coords.npix, coords.npix), dtype=torch.double + ) output = layer(blank_packed_img) # make sure we got back the number of visibilities we expected assert output.shape == (nchan, len(uu)) # if the image cube was filled with zeros, then we should make sure this is true - assert output.detach().numpy() == approx( - np.zeros((nchan, len(uu))) - ) + assert output.detach().numpy() == approx(np.zeros((nchan, len(uu)))) def test_nufft_cached_predict_GPU(coords, baselines_1D): @@ -210,15 +210,15 @@ def test_nufft_cached_predict_GPU(coords, baselines_1D): np.zeros((nchan, len(uu)), dtype=np.complex128) ) + def plot_nufft_comparison(uu, vv, an_output, num_output, path): - """Plot and save a figure comparing the analytic and numerical FT points. - """ + """Plot and save a figure comparing the analytic and numerical FT points.""" qq = utils.torch2npy(torch.hypot(uu, vv)) * 1e-6 diff = num_output - an_output - - fig, ax = plt.subplots(nrows=4, sharex=True, figsize=(7,5)) + + fig, ax = plt.subplots(nrows=4, sharex=True, figsize=(7, 5)) ax[0].scatter(qq, an_output.real, s=3, label="analytic") ax[0].scatter(qq, num_output.real, s=1, label="NuFFT") ax[0].set_ylabel("Real") @@ -240,10 +240,9 @@ def plot_nufft_comparison(uu, vv, an_output, num_output, path): fig.savefig(path, dpi=300) - def test_nufft_accuracy_single_chan(coords, baselines_1D, tmp_path): - """Create a single-channel ImageCube using an analytic function for which we know - the true FT. + """Create a single-channel ImageCube using an analytic function for which we know + the true FT. Then use the NuFFT to FT and sample that image. Plot both and their difference. Assert that the NuFFT samples and the analytic FT samples are close. @@ -267,8 +266,10 @@ def test_nufft_accuracy_single_chan(coords, baselines_1D, tmp_path): an_output = utils.fourier_gaussian_lambda_arcsec(uu, vv, **gauss_kw) an_output = utils.torch2npy(an_output) - plot_nufft_comparison(uu, vv, an_output, num_output, tmp_path / "nufft_comparison.png") - + plot_nufft_comparison( + uu, vv, an_output, num_output, tmp_path / "nufft_comparison.png" + ) + # threshold based on visual inspection of plot assert num_output == approx(an_output, abs=2.5e-6) @@ -287,7 +288,9 @@ def test_nufft_cached_accuracy_single_chan(coords, baselines_1D, tmp_path): img_packed = utils.sky_gaussian_arcsec( coords.packed_x_centers_2D, coords.packed_y_centers_2D, **gauss_kw ) - img_packed_tensor = torch.tensor(img_packed[np.newaxis, :, :], requires_grad=True, dtype=torch.double) + img_packed_tensor = torch.tensor( + img_packed[np.newaxis, :, :], requires_grad=True, dtype=torch.double + ) # use the NuFFT to predict the values of the cube at the u,v locations num_output = layer(img_packed_tensor)[0] # take the channel dim out @@ -297,7 +300,9 @@ def test_nufft_cached_accuracy_single_chan(coords, baselines_1D, tmp_path): an_output = utils.fourier_gaussian_lambda_arcsec(uu, vv, **gauss_kw) an_output = utils.torch2npy(an_output) - plot_nufft_comparison(uu, vv, an_output, num_output, tmp_path / "nufft_cached_comparison.png") + plot_nufft_comparison( + uu, vv, an_output, num_output, tmp_path / "nufft_cached_comparison.png" + ) # threshold based on visual inspection of plot assert num_output == approx(an_output, abs=2e-8) @@ -324,13 +329,14 @@ def test_nufft_cached_accuracy_coil_broadcast(coords, baselines_1D, tmp_path): # broadcast to 5 channels -- the image will be the same for each img_packed_tensor = torch.tensor( img_packed[np.newaxis, :, :] * np.ones((nchan, coords.npix, coords.npix)), - requires_grad=True, dtype=torch.double + requires_grad=True, + dtype=torch.double, ) # use the NuFFT to predict the values of the cube at the u,v locations num_output = layer(img_packed_tensor) num_output = utils.torch2npy(num_output) - + # plot a single channel, to check ichan = 1 @@ -385,7 +391,11 @@ def test_nufft_cached_accuracy_batch_broadcast(coords, baselines_2D_t, tmp_path) an_output = utils.torch2npy(an_output) plot_nufft_comparison( - uu[ichan], vv[ichan], an_output, num_output[ichan], tmp_path / "nufft_cached_comparison.png" + uu[ichan], + vv[ichan], + an_output, + num_output[ichan], + tmp_path / "nufft_cached_comparison.png", ) # loop through each channel and assert that things are the same diff --git a/test/geometry_test.py b/test/geometry_test.py index 84ca6d42..06b5f526 100644 --- a/test/geometry_test.py +++ b/test/geometry_test.py @@ -11,14 +11,15 @@ def test_rotate_points(): xs = torch.tensor([0.0, 1.0, 2.0]) ys = torch.tensor([1.0, -1.0, 2.0]) - omega = 35. * np.pi/180 - incl = 30. * np.pi/180 - Omega = 210. * np.pi/180 + omega = 35.0 * np.pi / 180 + incl = 30.0 * np.pi / 180 + Omega = 210.0 * np.pi / 180 X, Y = geometry.flat_to_observer(xs, ys, omega=omega, incl=incl, Omega=Omega) - xs_back, ys_back = geometry.observer_to_flat(X, Y, omega=omega, incl=incl, Omega=Omega) - + xs_back, ys_back = geometry.observer_to_flat( + X, Y, omega=omega, incl=incl, Omega=Omega + ) print("original", xs, ys) print("Observer", X, Y) @@ -29,13 +30,16 @@ def test_rotate_points(): def test_rotate_coords(coords): - - omega = 35. * np.pi/180 - incl = 30. * np.pi/180 - Omega = 210. * np.pi/180 - - x, y = geometry.observer_to_flat(coords.sky_x_centers_2D, coords.sky_y_centers_2D, omega=omega, incl=incl, Omega=Omega) + omega = 35.0 * np.pi / 180 + incl = 30.0 * np.pi / 180 + Omega = 210.0 * np.pi / 180 + + x, y = geometry.observer_to_flat( + coords.sky_x_centers_2D, + coords.sky_y_centers_2D, + omega=omega, + incl=incl, + Omega=Omega, + ) print(x, y) - - diff --git a/test/gridder_imager_test.py b/test/gridder_imager_test.py index 1590028a..8a992ec4 100644 --- a/test/gridder_imager_test.py +++ b/test/gridder_imager_test.py @@ -281,7 +281,7 @@ def test_grid_natural_arcsec2(imager, tmp_path): fig.savefig(tmp_path / "natural_v_robust_arcsec2.png", dpi=300) assert np.all(np.abs(beam_natural - beam_robust) < 1.5e-3) - assert np.all(np.abs(img_natural - img_robust) <2e-4) + assert np.all(np.abs(img_natural - img_robust) < 2e-4) plt.close("all") diff --git a/test/gridder_init_test.py b/test/gridder_init_test.py index d6639c8f..913633b9 100644 --- a/test/gridder_init_test.py +++ b/test/gridder_init_test.py @@ -30,11 +30,9 @@ def test_hermitian_pairs(mock_dataset_np): def test_averager_instantiate_cell_npix(mock_dataset_np): uu, vv, weight, data_re, data_im = mock_dataset_np - coords = coordinates.GridCoords( - cell_size=0.005, - npix=800 - ) - gridding.DataAverager(coords=coords, + coords = coordinates.GridCoords(cell_size=0.005, npix=800) + gridding.DataAverager( + coords=coords, uu=uu, vv=vv, weight=weight, diff --git a/test/input_output_test.py b/test/input_output_test.py index 3beb71dd..559f7942 100644 --- a/test/input_output_test.py +++ b/test/input_output_test.py @@ -1,4 +1,3 @@ - # from astropy.utils.data import download_file # from mpol.input_output import ProcessFitsImage diff --git a/test/losses_test.py b/test/losses_test.py index 3ffcbc1b..a8dacf59 100644 --- a/test/losses_test.py +++ b/test/losses_test.py @@ -24,9 +24,7 @@ def gridded_vis_model(coords, packed_cube): return flayer(packed_cube) -def test_chi_squared_evaluation( - loose_vis_model, mock_data_t, weight_2D_t -): +def test_chi_squared_evaluation(loose_vis_model, mock_data_t, weight_2D_t): # because of the way likelihood functions are defined, we would not expect # the loose chi_squared or log_likelihood function to give the same answers as # the gridded chi_squared or log_likelihood functions. This is because the normalization @@ -47,6 +45,7 @@ def test_log_likelihood_loose(loose_vis_model, mock_data_t, weight_2D_t): # calculate the ungridded log likelihood losses.log_likelihood(loose_vis_model, mock_data_t, weight_2D_t) + def test_log_likelihood_gridded(gridded_vis_model, dataset): losses.log_likelihood_gridded(gridded_vis_model, dataset) diff --git a/test/onedim_test.py b/test/onedim_test.py index da395c66..70c7b7eb 100644 --- a/test/onedim_test.py +++ b/test/onedim_test.py @@ -14,10 +14,14 @@ def test_radialI(mock_1d_image_model, tmp_path): rtest, itest = radialI(icube, geom, bins=bins) - fig, ax = plt.subplots(ncols=2, figsize=(10,5)) + fig, ax = plt.subplots(ncols=2, figsize=(10, 5)) - plot_image(np.squeeze(torch2npy(icube.sky_cube)), extent=icube.coords.img_ext, - ax=ax[0], clab="Jy / sr") + plot_image( + np.squeeze(torch2npy(icube.sky_cube)), + extent=icube.coords.img_ext, + ax=ax[0], + clab="Jy / sr", + ) ax[1].plot(rtrue, itrue, "k", label="truth") ax[1].plot(rtest, itest, "r.-", label="recovery") @@ -32,35 +36,108 @@ def test_radialI(mock_1d_image_model, tmp_path): plt.close("all") expected = [ - 6.40747314e+10, 4.01920507e+10, 1.44803534e+10, 2.94238627e+09, - 1.28782935e+10, 2.68613199e+10, 2.26564596e+10, 1.81151845e+10, - 1.52128965e+10, 1.05640352e+10, 1.33411204e+10, 1.61124502e+10, - 1.41500539e+10, 1.20121195e+10, 1.11770326e+10, 1.19676913e+10, - 1.20941686e+10, 1.09498286e+10, 9.74236410e+09, 7.99589196e+09, - 5.94787809e+09, 3.82074946e+09, 1.80823933e+09, 4.48414819e+08, - 3.17808840e+08, 5.77317876e+08, 3.98851281e+08, 8.06459834e+08, - 2.88706161e+09, 6.09577814e+09, 6.98556762e+09, 4.47436415e+09, - 1.89511273e+09, 5.96604356e+08, 3.44571640e+08, 5.65906765e+08, - 2.85854589e+08, 2.67589013e+08, 3.98357054e+08, 2.97052261e+08, - 3.82744591e+08, 3.52239791e+08, 2.74336969e+08, 2.28425747e+08, - 1.82290043e+08, 3.16077299e+08, 1.18465538e+09, 3.32239287e+09, - 5.26718846e+09, 5.16458748e+09, 3.58114198e+09, 2.13431954e+09, - 1.40936556e+09, 1.04032244e+09, 9.24050422e+08, 8.46829316e+08, - 6.80909295e+08, 6.83812465e+08, 6.91856237e+08, 5.29227136e+08, - 3.97557293e+08, 3.54893419e+08, 2.60997039e+08, 2.09306498e+08, - 1.93930693e+08, 6.97032407e+07, 6.66090083e+07, 1.40079594e+08, - 7.21775931e+07, 3.23902663e+07, 3.35932300e+07, 7.63318789e+06, - 1.29740981e+07, 1.44300351e+07, 8.06249624e+06, 5.85567843e+06, - 1.42637174e+06, 3.21445075e+06, 1.83763663e+06, 1.16926652e+07, - 2.46918188e+07, 1.60206523e+07, 3.26596592e+06, 1.27837319e+05, - 2.27104612e+04, 4.77267063e+03, 2.90467640e+03, 2.88482230e+03, - 1.43402521e+03, 1.54791996e+03, 7.23397046e+02, 1.02561351e+03, - 5.24845888e+02, 1.47320552e+03, 7.40419174e+02, 4.59029378e-03, - 0.00000000e+00, 0.00000000e+00, 0.00000000e+00 - ] - - np.testing.assert_allclose(itest, expected, rtol=1e-6, - err_msg="test_radialI") + 6.40747314e10, + 4.01920507e10, + 1.44803534e10, + 2.94238627e09, + 1.28782935e10, + 2.68613199e10, + 2.26564596e10, + 1.81151845e10, + 1.52128965e10, + 1.05640352e10, + 1.33411204e10, + 1.61124502e10, + 1.41500539e10, + 1.20121195e10, + 1.11770326e10, + 1.19676913e10, + 1.20941686e10, + 1.09498286e10, + 9.74236410e09, + 7.99589196e09, + 5.94787809e09, + 3.82074946e09, + 1.80823933e09, + 4.48414819e08, + 3.17808840e08, + 5.77317876e08, + 3.98851281e08, + 8.06459834e08, + 2.88706161e09, + 6.09577814e09, + 6.98556762e09, + 4.47436415e09, + 1.89511273e09, + 5.96604356e08, + 3.44571640e08, + 5.65906765e08, + 2.85854589e08, + 2.67589013e08, + 3.98357054e08, + 2.97052261e08, + 3.82744591e08, + 3.52239791e08, + 2.74336969e08, + 2.28425747e08, + 1.82290043e08, + 3.16077299e08, + 1.18465538e09, + 3.32239287e09, + 5.26718846e09, + 5.16458748e09, + 3.58114198e09, + 2.13431954e09, + 1.40936556e09, + 1.04032244e09, + 9.24050422e08, + 8.46829316e08, + 6.80909295e08, + 6.83812465e08, + 6.91856237e08, + 5.29227136e08, + 3.97557293e08, + 3.54893419e08, + 2.60997039e08, + 2.09306498e08, + 1.93930693e08, + 6.97032407e07, + 6.66090083e07, + 1.40079594e08, + 7.21775931e07, + 3.23902663e07, + 3.35932300e07, + 7.63318789e06, + 1.29740981e07, + 1.44300351e07, + 8.06249624e06, + 5.85567843e06, + 1.42637174e06, + 3.21445075e06, + 1.83763663e06, + 1.16926652e07, + 2.46918188e07, + 1.60206523e07, + 3.26596592e06, + 1.27837319e05, + 2.27104612e04, + 4.77267063e03, + 2.90467640e03, + 2.88482230e03, + 1.43402521e03, + 1.54791996e03, + 7.23397046e02, + 1.02561351e03, + 5.24845888e02, + 1.47320552e03, + 7.40419174e02, + 4.59029378e-03, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + ] + + np.testing.assert_allclose(itest, expected, rtol=1e-6, err_msg="test_radialI") def test_radialV(mock_1d_vis_model, tmp_path): @@ -68,11 +145,11 @@ def test_radialV(mock_1d_vis_model, tmp_path): fcube, Vtrue_dep, q_dep, geom = mock_1d_vis_model - bins = np.linspace(1,5e3,100) + bins = np.linspace(1, 5e3, 100) qtest, Vtest = radialV(fcube, geom, rescale_flux=True, bins=bins) - fig, ax = plt.subplots(ncols=1, nrows=2, figsize=(10,10)) + fig, ax = plt.subplots(ncols=1, nrows=2, figsize=(10, 10)) ax[0].plot(q_dep / 1e6, Vtrue_dep.real, "k.", label="truth deprojected") ax[0].plot(qtest / 1e3, Vtest.real, "r.-", label="recovery") @@ -92,32 +169,105 @@ def test_radialV(mock_1d_vis_model, tmp_path): plt.close("all") expected = [ - -9.61751019e+09, 2.75229026e+09, -4.36137738e+08, -2.30171445e+07, - -2.10099938e+08, 2.86360366e+08, -1.37544187e+07, -3.62764471e+07, - 1.94332782e+07, -4.63579878e+07, 4.38157379e+07, -1.19891002e+07, - 2.47285137e+07, -3.43389203e+07, 7.49974578e+05, 3.68423107e+06, - 9.43443498e+06, -1.16182426e+07, 1.08867793e+07, -8.74943322e+06, - 1.14521810e+07, -6.36361380e+06, 3.58538842e+05, -5.96714707e+06, - 1.04348614e+07, -1.47220982e+06, -1.19522309e+07, -4.09776593e+06, - 7.86540505e+06, 3.60337006e+06, -8.30025685e+06, 4.05093017e+06, - 3.33292357e+06, 2.05733741e+06, -7.65245396e+06, 3.73332165e+06, - 3.40645897e+06, -4.58494946e+06, 3.66101584e+06, -3.69627118e+06, - 5.27955178e+06, 9.75812262e+06, -1.65425072e+07, 5.47225658e+06, - -3.49680316e+06, 8.22030443e+06, -7.32448474e+06, -4.23843848e+06, - 1.27346507e+07, -4.60792496e+06, -2.56148856e+06, 6.29770245e+05, - -2.25521550e+06, 5.35018477e+06, -4.61334469e+06, 3.09166148e+06, - -9.18155255e+05, -1.00736465e+06, 1.12177040e+06, -9.21570359e+05, - 8.70817075e+05, 3.16472432e+04, -1.59681139e+06, 1.16213263e+06, - 3.64004059e+04, -8.49130119e+04, -2.30599556e+05, -1.59965392e+03, - 9.30837779e+05, -3.90387012e+05, -4.75338516e+05, 7.53183050e+04, - 3.41897054e+05, -7.53936979e+05, 1.99039974e+06, -1.90488504e+06, - -4.19283666e+05, 1.53004765e+06, -8.55774990e+05, 6.21661335e+05, - -5.00689314e+05, -6.26249184e+05, 1.94062725e+06, -1.65756778e+06, - 1.03046094e+06, -7.77307547e+05, 1.65177536e+05, 1.07726803e+05, - -2.46681205e+05, 1.18707317e+05, 7.05201176e+04, 1.39152470e+05, - -2.80631868e+04, 4.92257795e+05, -1.52044894e+06, 1.02459630e+06, - 8.42494484e+05, -1.57362080e+06, 7.22603120e+05 - ] - - np.testing.assert_allclose(Vtest.real, expected, rtol=1e-6, - err_msg="test_radialV") + -9.61751019e09, + 2.75229026e09, + -4.36137738e08, + -2.30171445e07, + -2.10099938e08, + 2.86360366e08, + -1.37544187e07, + -3.62764471e07, + 1.94332782e07, + -4.63579878e07, + 4.38157379e07, + -1.19891002e07, + 2.47285137e07, + -3.43389203e07, + 7.49974578e05, + 3.68423107e06, + 9.43443498e06, + -1.16182426e07, + 1.08867793e07, + -8.74943322e06, + 1.14521810e07, + -6.36361380e06, + 3.58538842e05, + -5.96714707e06, + 1.04348614e07, + -1.47220982e06, + -1.19522309e07, + -4.09776593e06, + 7.86540505e06, + 3.60337006e06, + -8.30025685e06, + 4.05093017e06, + 3.33292357e06, + 2.05733741e06, + -7.65245396e06, + 3.73332165e06, + 3.40645897e06, + -4.58494946e06, + 3.66101584e06, + -3.69627118e06, + 5.27955178e06, + 9.75812262e06, + -1.65425072e07, + 5.47225658e06, + -3.49680316e06, + 8.22030443e06, + -7.32448474e06, + -4.23843848e06, + 1.27346507e07, + -4.60792496e06, + -2.56148856e06, + 6.29770245e05, + -2.25521550e06, + 5.35018477e06, + -4.61334469e06, + 3.09166148e06, + -9.18155255e05, + -1.00736465e06, + 1.12177040e06, + -9.21570359e05, + 8.70817075e05, + 3.16472432e04, + -1.59681139e06, + 1.16213263e06, + 3.64004059e04, + -8.49130119e04, + -2.30599556e05, + -1.59965392e03, + 9.30837779e05, + -3.90387012e05, + -4.75338516e05, + 7.53183050e04, + 3.41897054e05, + -7.53936979e05, + 1.99039974e06, + -1.90488504e06, + -4.19283666e05, + 1.53004765e06, + -8.55774990e05, + 6.21661335e05, + -5.00689314e05, + -6.26249184e05, + 1.94062725e06, + -1.65756778e06, + 1.03046094e06, + -7.77307547e05, + 1.65177536e05, + 1.07726803e05, + -2.46681205e05, + 1.18707317e05, + 7.05201176e04, + 1.39152470e05, + -2.80631868e04, + 4.92257795e05, + -1.52044894e06, + 1.02459630e06, + 8.42494484e05, + -1.57362080e06, + 7.22603120e05, + ] + + np.testing.assert_allclose(Vtest.real, expected, rtol=1e-6, err_msg="test_radialV") diff --git a/test/plot_test.py b/test/plot_test.py index 736df7ed..c1551791 100644 --- a/test/plot_test.py +++ b/test/plot_test.py @@ -1,5 +1,3 @@ - - # from mpol.plot import image_comparison_fig # def test_image_comparison_fig(coords, tmp_path): diff --git a/test/plot_utils.py b/test/plot_utils.py index 39294f7a..ddd8112d 100644 --- a/test/plot_utils.py +++ b/test/plot_utils.py @@ -31,7 +31,8 @@ def extend_list(l, num=2): return num * l else: return l[:num] - + + def extend_kwargs(kwargs): """ This is a helper routine for imshow_two, designed to flexibly consume a variety @@ -39,19 +40,20 @@ def extend_kwargs(kwargs): kwargs: dict the kwargs dict provided from the function call - + Returns ------- dict Updated kwargs with length 2 lists of items. """ - + for key, item in kwargs.items(): kwargs[key] = extend_list(item) + def imshow_two(path, imgs, sky=False, suptitle=None, **kwargs): """Plot two images side by side, with scalebars. - + imgs is a list Parameters ---------- @@ -65,8 +67,8 @@ def imshow_two(path, imgs, sky=False, suptitle=None, **kwargs): if provided, list of strings corresponding to title for each subplot. If only one provided, xlabel: list if provided, list of strings - - + + Returns ------- None @@ -85,28 +87,28 @@ def imshow_two(path, imgs, sky=False, suptitle=None, **kwargs): cax_height = ax_height yy = bmargin + ax_height + tmargin - with mpl.rc_context({'figure.autolayout': False}): + with mpl.rc_context({"figure.autolayout": False}): fig = plt.figure(figsize=(xx, yy)) ax = [] cax = [] - - extend_kwargs(kwargs) + + extend_kwargs(kwargs) if "extent" not in kwargs: kwargs["extent"] = [None, None] - + for i in [0, 1]: a = fig.add_axes( - [ - (lmargin + i * (ax_width + middle_sep)) / xx, - bmargin / yy, - ax_width / xx, - ax_height / yy, - ] - ) + [ + (lmargin + i * (ax_width + middle_sep)) / xx, + bmargin / yy, + ax_width / xx, + ax_height / yy, + ] + ) ax.append(a) - + ca = fig.add_axes( ( [ @@ -118,13 +120,18 @@ def imshow_two(path, imgs, sky=False, suptitle=None, **kwargs): ) ) cax.append(ca) - + img = imgs[i] img = img.detach().numpy() if torch.is_tensor(img) else img - im = a.imshow(np.squeeze(img), origin="lower", interpolation="none", extent=kwargs["extent"][i]) + im = a.imshow( + np.squeeze(img), + origin="lower", + interpolation="none", + extent=kwargs["extent"][i], + ) plt.colorbar(im, cax=ca) - + if "title" in kwargs: a.set_title(kwargs["title"][i]) diff --git a/test/utils_test.py b/test/utils_test.py index 5fe41ad1..0604a545 100644 --- a/test/utils_test.py +++ b/test/utils_test.py @@ -130,7 +130,7 @@ def test_loglinspace(): def test_get_optimal_image_properties(baselines_1D): # test that get_optimal_image_properties returns sensible cell_size, npix - image_width = 5.0 # [arcsec] + image_width = 5.0 # [arcsec] u, v = baselines_1D @@ -138,4 +138,4 @@ def test_get_optimal_image_properties(baselines_1D): max_data_freq = max(abs(u).max(), abs(v).max()) - assert(utils.get_max_spatial_freq(cell_size, npix) >= max_data_freq) + assert utils.get_max_spatial_freq(cell_size, npix) >= max_data_freq From ab7f9f982be07819235db0fff37783c1ec82b249 Mon Sep 17 00:00:00 2001 From: Ian Czekala Date: Mon, 25 May 2026 15:32:01 +1000 Subject: [PATCH 3/3] move module import to top of file. --- src/mpol/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mpol/__init__.py b/src/mpol/__init__.py index c5963cf1..2863e8a1 100644 --- a/src/mpol/__init__.py +++ b/src/mpol/__init__.py @@ -1,8 +1,8 @@ -zenodo_record = 10064221 - import importlib.metadata try: __version__ = importlib.metadata.version("MPoL") except importlib.metadata.PackageNotFoundError: __version__ = "unknown" + +zenodo_record = 10064221