fitscube 2.3.2__tar.gz → 2.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {fitscube-2.3.2 → fitscube-2.4.1}/.github/workflows/cd.yml +4 -4
  2. {fitscube-2.3.2 → fitscube-2.4.1}/.github/workflows/ci.yml +5 -5
  3. {fitscube-2.3.2 → fitscube-2.4.1}/PKG-INFO +1 -1
  4. {fitscube-2.3.2 → fitscube-2.4.1}/fitscube/__init__.py +2 -1
  5. {fitscube-2.3.2 → fitscube-2.4.1}/fitscube/_version.py +2 -2
  6. {fitscube-2.3.2 → fitscube-2.4.1}/fitscube/bounding_box.py +60 -8
  7. {fitscube-2.3.2 → fitscube-2.4.1}/fitscube/combine_fits.py +284 -90
  8. fitscube-2.4.1/fitscube/exceptions.py +25 -0
  9. {fitscube-2.3.2 → fitscube-2.4.1}/tests/conftest.py +70 -0
  10. fitscube-2.4.1/tests/test_combine.py +44 -0
  11. fitscube-2.4.1/tests/test_combine_guards.py +381 -0
  12. {fitscube-2.3.2 → fitscube-2.4.1}/tests/test_times.py +0 -41
  13. fitscube-2.3.2/fitscube/exceptions.py +0 -13
  14. {fitscube-2.3.2 → fitscube-2.4.1}/.github/CONTRIBUTING.md +0 -0
  15. {fitscube-2.3.2 → fitscube-2.4.1}/.github/dependabot.yml +0 -0
  16. {fitscube-2.3.2 → fitscube-2.4.1}/.github/release.yml +0 -0
  17. {fitscube-2.3.2 → fitscube-2.4.1}/.gitignore +0 -0
  18. {fitscube-2.3.2 → fitscube-2.4.1}/.pre-commit-config.yaml +0 -0
  19. {fitscube-2.3.2 → fitscube-2.4.1}/CHANGELOG.md +0 -0
  20. {fitscube-2.3.2 → fitscube-2.4.1}/LICENSE +0 -0
  21. {fitscube-2.3.2 → fitscube-2.4.1}/README.md +0 -0
  22. {fitscube-2.3.2 → fitscube-2.4.1}/fitscube/asyncio.py +0 -0
  23. {fitscube-2.3.2 → fitscube-2.4.1}/fitscube/cli.py +0 -0
  24. {fitscube-2.3.2 → fitscube-2.4.1}/fitscube/extract.py +0 -0
  25. {fitscube-2.3.2 → fitscube-2.4.1}/fitscube/logging.py +0 -0
  26. {fitscube-2.3.2 → fitscube-2.4.1}/fitscube/version.pyi +0 -0
  27. {fitscube-2.3.2 → fitscube-2.4.1}/noxfile.py +0 -0
  28. {fitscube-2.3.2 → fitscube-2.4.1}/pyproject.toml +0 -0
  29. {fitscube-2.3.2 → fitscube-2.4.1}/tests/__init__.py +0 -0
  30. {fitscube-2.3.2 → fitscube-2.4.1}/tests/data/cube.zip +0 -0
  31. {fitscube-2.3.2 → fitscube-2.4.1}/tests/data/images.zip +0 -0
  32. {fitscube-2.3.2 → fitscube-2.4.1}/tests/data/time_images.zip +0 -0
  33. {fitscube-2.3.2 → fitscube-2.4.1}/tests/data/timecube.zip +0 -0
  34. {fitscube-2.3.2 → fitscube-2.4.1}/tests/test_bb.py +0 -0
  35. {fitscube-2.3.2 → fitscube-2.4.1}/tests/test_extract.py +0 -0
  36. {fitscube-2.3.2 → fitscube-2.4.1}/tests/test_frequencies.py +0 -0
  37. {fitscube-2.3.2 → fitscube-2.4.1}/tests/test_package.py +0 -0
@@ -25,11 +25,11 @@ jobs:
25
25
  runs-on: ubuntu-latest
26
26
 
27
27
  steps:
28
- - uses: actions/checkout@v5
28
+ - uses: actions/checkout@v7
29
29
  with:
30
30
  fetch-depth: 0
31
31
 
32
- - uses: hynek/build-and-inspect-python-package@v2
32
+ - uses: hynek/build-and-inspect-python-package@v2.18.0
33
33
 
34
34
  publish:
35
35
  needs: [dist]
@@ -43,13 +43,13 @@ jobs:
43
43
  if: github.event_name == 'release' && github.event.action == 'published'
44
44
 
45
45
  steps:
46
- - uses: actions/download-artifact@v5
46
+ - uses: actions/download-artifact@v8
47
47
  with:
48
48
  name: Packages
49
49
  path: dist
50
50
 
51
51
  - name: Generate artifact attestation for sdist and wheel
52
- uses: actions/attest-build-provenance@v2.4.0
52
+ uses: actions/attest-build-provenance@v4.1.1
53
53
  with:
54
54
  subject-path: "dist/*"
55
55
 
@@ -21,10 +21,10 @@ jobs:
21
21
  name: Format
22
22
  runs-on: ubuntu-latest
23
23
  steps:
24
- - uses: actions/checkout@v5
24
+ - uses: actions/checkout@v7
25
25
  with:
26
26
  fetch-depth: 0
27
- - uses: actions/setup-python@v5
27
+ - uses: actions/setup-python@v7
28
28
  with:
29
29
  python-version: "3.x"
30
30
  - uses: pre-commit/action@v3.0.1
@@ -42,11 +42,11 @@ jobs:
42
42
  runs-on: [ubuntu-latest]
43
43
 
44
44
  steps:
45
- - uses: actions/checkout@v5
45
+ - uses: actions/checkout@v7
46
46
  with:
47
47
  fetch-depth: 0
48
48
 
49
- - uses: actions/setup-python@v5
49
+ - uses: actions/setup-python@v7
50
50
  with:
51
51
  python-version: ${{ matrix.python-version }}
52
52
  allow-prereleases: true
@@ -62,6 +62,6 @@ jobs:
62
62
  --durations=20
63
63
 
64
64
  - name: Upload coverage report
65
- uses: codecov/codecov-action@v5.5.0
65
+ uses: codecov/codecov-action@v7.0.0
66
66
  with:
67
67
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fitscube
3
- Version: 2.3.2
3
+ Version: 2.4.1
4
4
  Summary: A package to produce produce FITS cubes.
5
5
  Project-URL: Homepage, https://github.com/AlecThomson/fitscube
6
6
  Project-URL: Bug Tracker, https://github.com/AlecThomson/fitscube/issues
@@ -2,8 +2,9 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from fitscube.bounding_box import BoundingBox, get_common_bounding_box
5
6
  from fitscube.combine_fits import combine_fits
6
7
 
7
8
  from ._version import version as __version__
8
9
 
9
- __all__ = ["__version__", "combine_fits"]
10
+ __all__ = ["BoundingBox", "__version__", "combine_fits", "get_common_bounding_box"]
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.3.2'
22
- __version_tuple__ = version_tuple = (2, 3, 2)
21
+ __version__ = version = '2.4.1'
22
+ __version_tuple__ = version_tuple = (2, 4, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -10,28 +10,43 @@ from pathlib import Path
10
10
  import numpy as np
11
11
  from astropy.io import fits
12
12
 
13
+ from fitscube.asyncio import gather_with_limit, sync_wrapper
13
14
  from fitscube.logging import logger
14
15
 
15
16
 
16
17
  @dataclass(frozen=True)
17
18
  class BoundingBox:
18
- """Simple container to represent a bounding box. Maximum values can be
19
- used as is when slicing."""
19
+ """Simple container to represent a bounding box.
20
+
21
+ .. warning::
22
+ ``x`` and ``y`` here are the *numpy* axes, which are the reverse of the
23
+ FITS ``NAXIS`` convention:
24
+
25
+ - ``x`` is axis ``-2`` -- image rows -- which is ``NAXIS2`` (declination)
26
+ - ``y`` is axis ``-1`` -- image columns -- which is ``NAXIS1`` (right ascension)
27
+
28
+ So a plane is sliced as ``data[..., xmin:xmax, ymin:ymax]`` and the
29
+ trimmed header takes ``NAXIS1 = y_span``, ``NAXIS2 = x_span``.
30
+
31
+ Minimum values are inclusive and maximum values are exclusive, so both can
32
+ be used as is when slicing. Note that ``xmax``/``ymax`` were *inclusive*
33
+ prior to ``v2.3.2``.
34
+ """
20
35
 
21
36
  xmin: int
22
- """Minimum x pixel"""
37
+ """Minimum row pixel (numpy axis -2, FITS NAXIS2). Inclusive."""
23
38
  xmax: int
24
- """Maximum x pixel"""
39
+ """Maximum row pixel (numpy axis -2, FITS NAXIS2). Can be used as is in a slice (e.g. is exclusive)."""
25
40
  ymin: int
26
- """Minimum y pixel. Can be used as is in slice (e.g. is exclusive). """
41
+ """Minimum column pixel (numpy axis -1, FITS NAXIS1). Inclusive."""
27
42
  ymax: int
28
- """Maximum y pixel Can be used as is in slice (e.g. is exclusive)."""
43
+ """Maximum column pixel (numpy axis -1, FITS NAXIS1). Can be used as is in a slice (e.g. is exclusive)."""
29
44
  original_shape: tuple[int, int]
30
45
  """The original shape of the image. If constructed against a cube this is the shape of a single plane."""
31
46
  y_span: int
32
- """The span between ymax and ymin"""
47
+ """The span between ymax and ymin (i.e. the trimmed NAXIS1)"""
33
48
  x_span: int
34
- """The span between xmax and xmin"""
49
+ """The span between xmax and xmin (i.e. the trimmed NAXIS2)"""
35
50
 
36
51
 
37
52
  def create_bound_box_plane(image_data: np.ndarray) -> BoundingBox | None:
@@ -151,3 +166,40 @@ async def get_bounding_box_for_fits_coro(
151
166
  data[data == 0.0] = np.nan
152
167
 
153
168
  return await asyncio.to_thread(create_bound_box_plane, image_data=data)
169
+
170
+
171
+ async def get_common_bounding_box_coro(
172
+ file_list: list[Path],
173
+ invalidate_zeros: bool = False,
174
+ max_workers: int | None = None,
175
+ ) -> BoundingBox:
176
+ """Compute the single bounding box that encompasses the valid data of every
177
+ image in ``file_list``.
178
+
179
+ This is the box that ``combine_fits`` computes internally when
180
+ ``bounding_box=True``. Compute it once with this function and pass the
181
+ result to ``combine_fits(bounding_box=...)`` when several cubes (e.g. an
182
+ image cube and its weights cube) must land on an identical pixel grid.
183
+
184
+ Args:
185
+ file_list (list[Path]): The FITS images to consider
186
+ invalidate_zeros (bool, optional): Mark pixels that are exactly 0.0 as invalid (NaN them). Defaults to False.
187
+ max_workers (int | None, optional): Maximum number of concurrent reads. Defaults to None.
188
+
189
+ Returns:
190
+ BoundingBox: The smallest bounding box that contains all valid data
191
+ """
192
+ boxes = await gather_with_limit(
193
+ max_workers,
194
+ *(
195
+ get_bounding_box_for_fits_coro(
196
+ fits_path=fits_path, invalidate_zeros=invalidate_zeros
197
+ )
198
+ for fits_path in file_list
199
+ ),
200
+ desc="Bounding boxes",
201
+ )
202
+ return extract_common_bounding_box(bounding_boxes=boxes)
203
+
204
+
205
+ get_common_bounding_box = sync_wrapper(get_common_bounding_box_coro)