218 image analysis and image plotting helper refactor#219
Conversation
…new docstring on that and dask config
Normalise DataArrays with named x/y dims to (x, y) axis order before resolving coordinates and labels, so callers using the common astronomy (y, x) storage layout get correct plot axes and labels. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WCSAxes derives correct RA/Dec labels from the WCS object itself. Only call set_axislabel() in the WCS branch when the caller explicitly passed a string coordinate selector as a custom label. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Non-monotonic world-coordinate values in coord are intentionally allowed; only the synthetic pixel-index axis must be strictly increasing. Update the Returns and Notes sections to reflect the actual preconditions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 11 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import dask.array as da | ||
|
|
||
| from ...utils._gaussian_math import ( | ||
| FWHM2SIG as _FWHM2SIG, |
There was a problem hiding this comment.
_FWHM2SIG is imported from astroviper.utils._gaussian_math but never used in this module. Please drop the unused import (or use it) to avoid lint failures / dead imports.
| FWHM2SIG as _FWHM2SIG, |
| def _coord_from_spec(spec, dim_name: str, axis_size: int) -> np.ndarray: | ||
| if spec is None: | ||
| if dim_name in data.coords: | ||
| coord_values = np.asarray(data.coords[dim_name].values, dtype=float) | ||
| else: | ||
| coord_values = np.arange(axis_size, dtype=float) | ||
| elif isinstance(spec, str): | ||
| if spec not in data.coords: | ||
| raise ValueError(f"Coordinate {spec!r} not found in DataArray.") | ||
| coord_values = np.asarray(data.coords[spec].values, dtype=float) | ||
| else: | ||
| coord_values = np.asarray(spec, dtype=float) |
There was a problem hiding this comment.
_resolve_plot_coords() coerces DataArray coordinate values to dtype=float even when show_world_axes=False (pixel/imshow mode), which can raise for legitimate non-numeric coords (e.g., datetime64 or string labels) even though those coords are not needed for pixel plotting. Consider only resolving/casting x/y coordinate vectors when show_world_axes=True and wcs is None, or avoid forcing dtype=float unless the coordinates are actually used for pcolormesh/world-axis rendering.
tests/benchmark/distributed/image_analysis/benchmark_multi_gaussian2d_fit.py
Outdated
Show resolved
Hide resolved
tests/benchmark/distributed/image_analysis/benchmark_multi_gaussian2d_fit.py
Outdated
Show resolved
Hide resolved
| def make_scene_via_component_models( | ||
| nx: int, | ||
| ny: int, | ||
| components: list[dict], | ||
| *, | ||
| offset: float = 0.1, | ||
| noise_std: float = 0.02, | ||
| seed: int | None = None, | ||
| coords: bool = True, | ||
| angle: str = "math", # "math" | "pa" | "auto" — same semantics as astroviper's model | ||
| x_world: tuple[float, float] = (0.0, 1.0), | ||
| y_world: tuple[float, float] = (0.0, 1.0), | ||
| ) -> xr.DataArray: |
There was a problem hiding this comment.
make_scene_via_component_models() annotates components as list[dict], but the implementation treats it as either a list-of-dicts (single-channel) or a list-of-lists-of-dicts (multi-channel) via isinstance(components[0], dict). Tightening the type annotation (or simplifying the logic to match the annotation) would make the intended input contract clearer and avoid confusion for benchmark users.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add need_coords flag to _resolve_plot_coords and set it only for the pcolormesh (show_world_axes=True, no WCS) branch where coordinate vectors are actually consumed. Other rendering modes no longer attempt a float cast on DataArray coordinates, so datetime64 or string labels no longer raise needlessly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ssian2d_fit.py Only set envs if run directly Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ssian2d_fit.py remove dead code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
https://github.com/casangi/astroviper into 218-image-analysis-and-image-plotting-helper-refactor
…dels The function accepts both list[dict] (single-channel) and list[list[dict]] (multi-channel); update the annotation and docstring to reflect both forms. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 11 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| values = np.asarray(data) | ||
| if values.ndim != 2: | ||
| raise ValueError( | ||
| "data must be a 2D array-like object with shape (nx, ny) for plotting." | ||
| ) | ||
|
|
||
| if not need_coords: | ||
| return ( | ||
| values, | ||
| np.arange(values.shape[0], dtype=float), | ||
| np.arange(values.shape[1], dtype=float), | ||
| ) |
There was a problem hiding this comment.
_resolve_plot_coords skips validation of x_coords/y_coords when need_coords=False, which means passing a string selector with a NumPy input (or in the WCS branch) is silently ignored instead of raising as documented. Consider validating x_coords/y_coords types up front (or at least rejecting str selectors when data is not an xarray.DataArray) so mistakes don’t slip through depending on show_world_axes/wcs.
tests/benchmark/distributed/image_analysis/benchmark_multi_gaussian2d_fit.py
Outdated
Show resolved
Hide resolved
tests/benchmark/distributed/image_analysis/benchmark_multi_gaussian2d_fit.py
Outdated
Show resolved
Hide resolved
String selectors were silently ignored when need_coords=False (imshow and WCS branches). Now validate them upfront in both the DataArray and plain-array paths so bad inputs always raise regardless of rendering mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ssian2d_fit.py remove dead code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ssian2d_fit.py remove dead code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
https://github.com/casangi/astroviper into 218-image-analysis-and-image-plotting-helper-refactor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 11 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/astroviper/utils/plotting.py
Outdated
| # Validate string selectors upfront so bad inputs always raise, | ||
| # regardless of whether coordinate vectors will be consumed. | ||
| for spec, label in ((x_coords, "x"), (y_coords, "y")): | ||
| if isinstance(spec, str) and spec not in data.coords: | ||
| raise ValueError(f"Coordinate {spec!r} not found in DataArray.") | ||
|
|
||
| if not need_coords: | ||
| # Coordinate vectors will not be used by the caller; skip the | ||
| # DataArray coord lookup and float cast to avoid raising on | ||
| # non-numeric coordinates (e.g. datetime64, string labels). |
There was a problem hiding this comment.
In the wcs plotting branch, need_coords is false and x_coords/y_coords are used only as optional axis-label overrides, but this code still requires the strings to exist in data.coords and raises otherwise. That makes it impossible to use x_coords='ra'/y_coords='dec' as pure labels unless the DataArray happens to expose coords with those exact names. Consider skipping this validation when need_coords=False (or splitting the API into separate x_label/y_label parameters) so WCS label overrides don’t depend on DataArray coordinate presence.
| # Validate string selectors upfront so bad inputs always raise, | |
| # regardless of whether coordinate vectors will be consumed. | |
| for spec, label in ((x_coords, "x"), (y_coords, "y")): | |
| if isinstance(spec, str) and spec not in data.coords: | |
| raise ValueError(f"Coordinate {spec!r} not found in DataArray.") | |
| if not need_coords: | |
| # Coordinate vectors will not be used by the caller; skip the | |
| # DataArray coord lookup and float cast to avoid raising on | |
| # non-numeric coordinates (e.g. datetime64, string labels). | |
| if not need_coords: | |
| # Coordinate vectors will not be used by the caller; skip the | |
| # DataArray coord lookup and float cast to avoid raising on | |
| # non-numeric coordinates (e.g. datetime64, string labels) and | |
| # to allow string x_coords / y_coords to be used as label-only | |
| # overrides by callers such as the WCS plotting path. |
| if not need_coords: | ||
| # Coordinate vectors will not be used by the caller; skip the | ||
| # DataArray coord lookup and float cast to avoid raising on | ||
| # non-numeric coordinates (e.g. datetime64, string labels). | ||
| return ( | ||
| np.asarray(data.values), | ||
| np.arange(nx, dtype=float), | ||
| np.arange(ny, dtype=float), | ||
| ) |
There was a problem hiding this comment.
np.asarray(data.values) forces immediate materialization of the full DataArray (and will compute Dask-backed arrays) even in modes where callers might reasonably expect plotting to accept already-realized NumPy inputs only. Since other parts of this PR explicitly avoid touching .values to preserve laziness, it would be more consistent to use np.asarray(data.data) plus an explicit np.asarray(... ) conversion at the plotting call sites (where materialization is truly required), or document clearly that generate_plot() always realizes the full array for plotting.
…=False In the WCS and imshow branches, x_coords/y_coords strings are label overrides and do not require a matching DataArray coordinate. Move the existence check inside the need_coords=True path where coords are actually looked up. The pcolormesh path (world axes, no WCS) still raises on missing coord names. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…boundary _resolve_plot_coords now returns data.data (raw underlying array) instead of calling np.asarray(data.values), keeping lazy Dask arrays un-computed until generate_plot explicitly materializes with np.asarray() at the single rendering boundary. Document the behaviour in both docstrings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 11 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _resolve_plot_coords( | ||
| data, | ||
| x_coords: Optional[Union[str, np.ndarray]] = None, | ||
| y_coords: Optional[Union[str, np.ndarray]] = None, | ||
| *, | ||
| need_coords: bool = True, | ||
| ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: | ||
| """ |
There was a problem hiding this comment.
_resolve_plot_coords is annotated to return tuple[np.ndarray, np.ndarray, np.ndarray], but in the xarray.DataArray path it returns data.data, which can be a Dask array (or other array-like) rather than a NumPy ndarray. This makes the type hint inaccurate and can trip static type checking; consider widening the first element’s type (e.g., np.ndarray | dask.array.Array or a more general ArrayLike) to match the docstring’s “NumPy or Dask” behavior.
The first element may be a NumPy ndarray or a Dask array depending on the DataArray's backing storage; Any accurately reflects this while the docstring explains the contract. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tting-helper-refactor 218 image analysis and image plotting helper refactor
This PR builds on PR 217. PR 217 should be merged before this one is.