foscat.Plot =========== .. py:module:: foscat.Plot Attributes ---------- .. autoapisummary:: foscat.Plot.ESRI_WORLD_IMAGERY Classes ------- .. autoapisummary:: foscat.Plot.TileCache Functions --------- .. autoapisummary:: foscat.Plot.lgnomproject foscat.Plot.plot_scat foscat.Plot.power_spectrum_1d foscat.Plot.estimate_psd_slope foscat.Plot.adjust_psd_slope foscat.Plot.latlon_to_xyz_frac foscat.Plot.xyz_frac_to_tile_pixel foscat.Plot.sample_esri_world_imagery foscat.Plot.get_half_interp_weights_ang_general foscat.Plot.conjugate_gradient_normal_equation foscat.Plot.spectrum_polar_to_cartesian foscat.Plot.plot_wave foscat.Plot.lonlat_edges_from_ref foscat.Plot.plot_image_lonlat foscat.Plot.plot_image_latlon Module Contents --------------- .. py:function:: lgnomproject(cell_ids, data, nside: int, rot=None, xsize: int = 400, ysize: int = 400, reso: float = None, fov_deg=None, nest: bool = True, reduce: str = 'mean', mask_outside: bool = True, unseen_value=None, return_image_only: bool = False, title: str = None, cmap: str = 'viridis', vmin=None, vmax=None, notext: bool = False, hold: bool = True, interp: bool = False, sub=(1, 1, 1), cbar: bool = False, unit: str = 'Value', rgb_clip=(0.0, 1.0)) Gnomonic projection from *sparse* HEALPix samples (cell_ids, data) to an image. Supports scalar data (N,) and RGB data (N,3). For RGB, colorbar/cmap/vmin/vmax are ignored. .. py:function:: plot_scat(s1, s2, s3, s4) .. py:function:: power_spectrum_1d(data, dx=1.0) Compute the isotropic 1D power spectrum of a 2D field. :Parameters: * **data** (:py:class:`ndarray (ny`, :py:class:`nx)`) -- Input 2D field. * **dx** (:py:class:`float`) -- Pixel size in the same spatial unit as desired frequency inverse. If dx is in meters, returned frequencies are in m^-1 (cycles per meter). :returns: * **f_centers** (:py:class:`ndarray`) -- Radial spatial frequencies (cycles per unit length), e.g., m^-1 if dx is in meters. * **Pk** (:py:class:`ndarray`) -- Azimuthally averaged power spectrum over radial frequency bins (arbitrary units unless you add a normalization). .. py:function:: estimate_psd_slope(img, dx=1.0, fmin_frac=0.02, fmax_frac=0.4) Estimate beta in P(f) ~ f^-beta from the isotropic 1D PSD (log-log linear fit). Uses the provided band [fmin_frac, fmax_frac] * f_max to avoid DC/Nyquist artifacts. .. py:function:: adjust_psd_slope(img, dx=1.0, delta_beta=0.0, f_ref=None, band=None, apodize=True, preserve_mean=True, match_variance=True, eps=None) Change the isotropic PSD slope by delta_beta (P -> P * f^{-delta_beta}). - delta_beta > 0 : steeper spectrum (more large-scale, smoother image) - delta_beta < 0 : flatter/whiter spectrum (more small-scale, rougher image) :Parameters: * **img** (:py:class:`2D array`) -- Input image. * **dx** (:py:class:`float`) -- Pixel size (e.g., meters). Frequencies are cycles per unit of dx. * **delta_beta** (:py:class:`float`) -- Desired slope change: P' ~ P * f^{-delta_beta}. * **f_ref** (:py:class:`float` or :py:obj:`None`) -- Reference frequency for normalization. If None, use median nonzero f. * **band** (:py:class:`tuple (f_lo`, :py:class:`f_hi)` or :py:obj:`None`) -- If set, apply the slope change only within [f_lo, f_hi] (cycles per unit); smooth edges. * **apodize** (:py:class:`bool`) -- Apply 2D Hann window before FFT to reduce edge ringing. * **preserve_mean** (:py:class:`bool`) -- Keep DC (mean) unchanged. * **match_variance** (:py:class:`bool`) -- Rescale output to match input variance. * **eps** (:py:class:`float` or :py:obj:`None`) -- Small positive to protect f=0. If None, set to 1/(max(n)*dx). :returns: **out** (:py:class:`2D array (real)`) -- Image with adjusted spectrum slope. .. py:function:: latlon_to_xyz_frac(lat, lon, z) Return fractional tile coordinates (xf, yf) at zoom z (Web Mercator). .. py:function:: xyz_frac_to_tile_pixel(xf, yf, tile_size=256) .. py:data:: ESRI_WORLD_IMAGERY :value: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}' .. py:class:: TileCache .. py:attribute:: cache .. py:attribute:: session .. py:method:: get_tile(z, x, y, timeout=10) .. py:function:: sample_esri_world_imagery(lat, lon, zoom=17, tile_size=256) lat, lon: arrays of shape (N,) zoom: Web Mercator zoom level returns: RGB uint8 array of shape (N, 3) .. py:function:: get_half_interp_weights_ang_general(nside_full, theta, phi, edge_mode='nearest') Bilinear weights from the 'half-level' lattice (EVEN pixels of full grid) to arbitrary directions. Returns I (4 even NESTED ids) and W (4 weights summing to 1). .. py:function:: conjugate_gradient_normal_equation(data, x0, www, all_idx, LPT=None, LP=None, max_iter=100, tol=1e-08, verbose=True) Solve the normal equation (Pᵗ P) x = Pᵗ y using the Conjugate Gradient method. :Parameters: * **data** (:py:class:`array_like`) -- Observed UTM data y ∈ ℝᵐ * **x0** (:py:class:`array_like`) -- Initial guess for solution x ∈ ℝⁿ (HEALPix domain) * **www** (:py:class:`interpolation weights`) * **all_idx** (:py:class:`interpolation indices`) * **LPT** (:py:class:`implementation` of :py:class:`adjoint operator Pᵗ`) * **LP** (:py:class:`implementation` of :py:class:`forward operator P`) * **max_iter** (:py:class:`maximum number` of :py:class:`CG iterations`) * **tol** (:py:class:`stopping tolerance on residual norm`) * **verbose** (:py:class:`print convergence info every 50 iterations`) :returns: **x** (:py:class:`estimated HEALPix solution u ∈ ℝⁿ`) .. py:function:: spectrum_polar_to_cartesian(w, scales=None, orientations=None, n_pixels=512, r_max=None, method='bilinear', fill_value=0.0, *, scale_kind='frequency', size_to_freq_factor=1.0) If scale_kind == "frequency": `scales` are already radii in frequency units, strictly increasing (low->high freq). If scale_kind == "size": `scales` are spatial sizes (e.g., km, px), strictly increasing (small->large size), and they are converted to frequency radii by: freq = size_to_freq_factor / size. Choose size_to_freq_factor to get the units you want (e.g., 1.0 for cycles/size). .. py:function:: plot_wave(wave, title='spectrum', unit='Amplitude', cmap='viridis') .. py:function:: lonlat_edges_from_ref(shape, ref_lon, ref_lat, dlon, dlat, anchor='center') Build lon/lat *edges* (H+1, W+1) for a regular, axis-aligned grid. :Parameters: * **shape** (:py:class:`tuple(int`, :py:class:`int)`) -- (H, W) of the image. * **ref_lon, ref_lat** (:py:class:`float`) -- Reference coordinate in degrees. Interpreted according to `anchor`. * **dlon, dlat** (:py:class:`float`) -- Pixel size in degrees along x (lon) and y (lat). Use positives. * **anchor** (``{"center","topleft","topright","bottomleft","bottomright"}``) -- Where (ref_lon, ref_lat) sits relative to the image. :returns: **lon_edges, lat_edges** (:py:class:`2D arrays` of :py:class:`shape (H+1`, :py:class:`W+1)`) -- Corner coordinates suitable for `pcolormesh`. .. py:function:: plot_image_lonlat(img, lon_edges, lat_edges, cmap='viridis', vmin=None, vmax=None) Plot a 2D image on a lon/lat grid using pcolormesh (no reprojection). .. py:function:: plot_image_latlon(fig, ax, img, lat, lon, mode='structured', cmap='viridis', vmin=None, vmax=None, shading='flat', aspect='equal') Plot an image given per-pixel lat/lon coordinates. :Parameters: * **img** (:py:class:`(H`, :py:class:`W) ndarray`) -- Image values per pixel. * **lat, lon** (:py:class:`(H`, :py:class:`W) ndarray`) -- Latitude and longitude at *pixel centers* (same shape as `img`). * **mode** (``{"structured", "scattered"}``) -- - "structured": (i, j) grid is regular (rectangular index space), possibly warped. We'll compute per-cell corners and use pcolormesh. - "scattered" : pixels are not on a regular (i, j) grid. We'll triangulate points and use tripcolor. * **cmap, vmin, vmax** (:py:class:`matplotlib colormap settings.`) * **shading** (``{"flat","gouraud"}`` for pcolormesh/tripcolor. ``"flat"`` = one color per cell/triangle.) * **aspect** (:py:class:`matplotlib aspect for axes`, e.g. ``"equal"`` or ``"auto"``:py:class:`.`) :returns: * **fig, ax** (:py:class:`matplotlib Figure` and :py:class:`Axes`) * **artist** (:py:class:`QuadMesh (structured)` or :py:class:`PolyCollection (scattered)`)