Coverage for python / lsst / images / _generalized_image.py: 73%
46 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-04 01:58 -0800
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-04 01:58 -0800
1# This file is part of lsst-images.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14__all__ = ("AbsoluteSliceProxy", "GeneralizedImage", "LocalSliceProxy")
16from abc import ABC, abstractmethod
17from functools import cached_property
18from types import EllipsisType
19from typing import Any, Self, TypeVar
21import astropy.wcs
23from ._geom import Box
24from ._transforms import Projection, ProjectionAstropyView
26T = TypeVar("T", bound="GeneralizedImage") # for sphinx
29class GeneralizedImage(ABC):
30 """A base class for types that represent one or more 2-d image-like arrays
31 with the same pixel grid and projection.
32 """
34 @property
35 @abstractmethod
36 def bbox(self) -> Box:
37 """Bounding box for the image (`Box`)."""
38 raise NotImplementedError()
40 @property
41 @abstractmethod
42 def projection(self) -> Projection[Any] | None:
43 """The projection that maps this image's pixel grid to the sky
44 (`Projection` | `None`).
46 Notes
47 -----
48 The pixel coordinates used by this projection account for the bounding
49 box ``start``; they are not just array indices.
50 """
51 raise NotImplementedError()
53 @property
54 def astropy_wcs(self) -> ProjectionAstropyView | None:
55 """An Astropy WCS for this image's pixel array.
57 Notes
58 -----
59 As expected for Astropy WCS objects, this defines pixel coordinates
60 such that the first row and column in any associated arrays are
61 ``(0, 0)``, not ``bbox.start``, as is the case for `projection`.
63 This object satisfies the `astropy.wcs.wcsapi.BaseHighLevelWCS` and
64 `astropy.wcs.wcsapi.BaseLowLevelWCS` interfaces, but it is not an
65 `astropy.wcs.WCS` (use `fits_wcs` for that).
66 """
67 return self.projection.as_astropy(self.bbox) if self.projection is not None else None
69 @cached_property
70 def fits_wcs(self) -> astropy.wcs.WCS | None:
71 """An Astropy FITS WCS for this image's pixel array.
73 Notes
74 -----
75 As expected for Astropy WCS objects, this defines pixel coordinates
76 such that the first row and column in any associated arrays are
77 ``(0, 0)``, not ``bbox.start``, as is the case for `projection`.
79 This may be an approximation or absent if `projection` is not
80 naturally representable as a FITS WCS.
81 """
82 return (
83 self.projection.as_fits_wcs(self.bbox, allow_approximation=True)
84 if self.projection is not None
85 else None
86 )
88 @property
89 def local(self) -> LocalSliceProxy[Self]:
90 """A proxy object for slicing a generalized image using "local" or
91 "array" pixel coordinates.
93 Notes
94 -----
95 In this convention, the first row and column of the pixel grid is
96 always at ``(0, 0)``. This is also the convention used by
97 `astropy.wcs` objects. When a subimage is created from a parent image,
98 its "local" coordinate system is offset from the original.
100 Note that most `lsst.images` types (e.g. `~lsst.images.Box`,
101 `~lsst.images.Projection`, `~lsst.images.psfs.PointSpreadFunction`)
102 operate instead in "absolute" coordinates, which is shared by subimage
103 and their parents.
105 See Also
106 --------
107 lsst.images.BoxSliceFactory
108 lsst.images.IntervalSliceFactory
109 """
110 return LocalSliceProxy(self)
112 @property
113 def absolute(self) -> AbsoluteSliceProxy[Self]:
114 """A proxy object for slicing a generalized image using absolute pixel
115 coordinates.
117 Notes
118 -----
119 In this convention, the first row and column of the pixel grid is
120 ``bbox.start``. A subimage and its parent image share the same
121 absolute pixel coordinate system, and most `lsst.images` types (e.g.
122 `~lsst.images.Box`, `~lsst.images.Projection`,
123 `~lsst.images.psfs.PointSpreadFunction`) operate exclusively in this
124 system.
126 Note that `astropy.wcs` and `numpy.ndarray` are not aware of the
127 ``bbox.start`` offset that defines tihs coordinates system; use
128 `local` slicing for indices obtained from those.
130 See Also
131 --------
132 lsst.images.BoxSliceFactory
133 lsst.images.IntervalSliceFactory
134 """
135 return AbsoluteSliceProxy(self)
137 # Subclasses should delegate to super().__getitem__ for some user-friendly
138 # argument type-checking before providing their own implementation.
139 @abstractmethod
140 def __getitem__(self, bbox: Box | EllipsisType) -> Self:
141 if not isinstance(bbox, Box):
142 raise TypeError(
143 "Only Box objects can be used to subset image objects directly; "
144 "use .local[y, x] or .absolute[y, x] proxies for slice-based subsets."
145 )
146 return self
148 @abstractmethod
149 def copy(self) -> Self:
150 """Deep-copy the image."""
151 raise NotImplementedError()
154class LocalSliceProxy[T: GeneralizedImage]:
155 """A proxy object for obtraining a generalized image subset using
156 local slicing.
158 See `GeneralizedImage.local` for more information.
159 """
161 def __init__(self, parent: T):
162 self._parent = parent
164 def __getitem__(self, slices: tuple[slice, slice]) -> T:
165 return self._parent[self._parent.bbox.local[slices]]
168class AbsoluteSliceProxy[T: GeneralizedImage]:
169 """A proxy object for obtraining a generalized image subset using
170 absolute slicing.
172 See `GeneralizedImage.absolute` for more information.
173 """
175 def __init__(self, parent: T):
176 self._parent = parent
178 def __getitem__(self, slices: tuple[slice, slice]) -> T:
179 return self._parent[self._parent.bbox.absolute[slices]]