fitscube 2.3.1__tar.gz → 2.3.2__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 (34) hide show
  1. {fitscube-2.3.1 → fitscube-2.3.2}/PKG-INFO +1 -1
  2. {fitscube-2.3.1 → fitscube-2.3.2}/fitscube/_version.py +2 -2
  3. {fitscube-2.3.1 → fitscube-2.3.2}/fitscube/combine_fits.py +38 -3
  4. {fitscube-2.3.1 → fitscube-2.3.2}/pyproject.toml +1 -0
  5. {fitscube-2.3.1 → fitscube-2.3.2}/tests/test_frequencies.py +14 -1
  6. {fitscube-2.3.1 → fitscube-2.3.2}/.github/CONTRIBUTING.md +0 -0
  7. {fitscube-2.3.1 → fitscube-2.3.2}/.github/dependabot.yml +0 -0
  8. {fitscube-2.3.1 → fitscube-2.3.2}/.github/release.yml +0 -0
  9. {fitscube-2.3.1 → fitscube-2.3.2}/.github/workflows/cd.yml +0 -0
  10. {fitscube-2.3.1 → fitscube-2.3.2}/.github/workflows/ci.yml +0 -0
  11. {fitscube-2.3.1 → fitscube-2.3.2}/.gitignore +0 -0
  12. {fitscube-2.3.1 → fitscube-2.3.2}/.pre-commit-config.yaml +0 -0
  13. {fitscube-2.3.1 → fitscube-2.3.2}/CHANGELOG.md +0 -0
  14. {fitscube-2.3.1 → fitscube-2.3.2}/LICENSE +0 -0
  15. {fitscube-2.3.1 → fitscube-2.3.2}/README.md +0 -0
  16. {fitscube-2.3.1 → fitscube-2.3.2}/fitscube/__init__.py +0 -0
  17. {fitscube-2.3.1 → fitscube-2.3.2}/fitscube/asyncio.py +0 -0
  18. {fitscube-2.3.1 → fitscube-2.3.2}/fitscube/bounding_box.py +0 -0
  19. {fitscube-2.3.1 → fitscube-2.3.2}/fitscube/cli.py +0 -0
  20. {fitscube-2.3.1 → fitscube-2.3.2}/fitscube/exceptions.py +0 -0
  21. {fitscube-2.3.1 → fitscube-2.3.2}/fitscube/extract.py +0 -0
  22. {fitscube-2.3.1 → fitscube-2.3.2}/fitscube/logging.py +0 -0
  23. {fitscube-2.3.1 → fitscube-2.3.2}/fitscube/version.pyi +0 -0
  24. {fitscube-2.3.1 → fitscube-2.3.2}/noxfile.py +0 -0
  25. {fitscube-2.3.1 → fitscube-2.3.2}/tests/__init__.py +0 -0
  26. {fitscube-2.3.1 → fitscube-2.3.2}/tests/conftest.py +0 -0
  27. {fitscube-2.3.1 → fitscube-2.3.2}/tests/data/cube.zip +0 -0
  28. {fitscube-2.3.1 → fitscube-2.3.2}/tests/data/images.zip +0 -0
  29. {fitscube-2.3.1 → fitscube-2.3.2}/tests/data/time_images.zip +0 -0
  30. {fitscube-2.3.1 → fitscube-2.3.2}/tests/data/timecube.zip +0 -0
  31. {fitscube-2.3.1 → fitscube-2.3.2}/tests/test_bb.py +0 -0
  32. {fitscube-2.3.1 → fitscube-2.3.2}/tests/test_extract.py +0 -0
  33. {fitscube-2.3.1 → fitscube-2.3.2}/tests/test_package.py +0 -0
  34. {fitscube-2.3.1 → fitscube-2.3.2}/tests/test_times.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fitscube
3
- Version: 2.3.1
3
+ Version: 2.3.2
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
@@ -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.1'
22
- __version_tuple__ = version_tuple = (2, 3, 1)
21
+ __version__ = version = '2.3.2'
22
+ __version_tuple__ = version_tuple = (2, 3, 2)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -132,6 +132,41 @@ def isin_close(
132
132
  return np.isclose(element[:, None], test_element, atol, rtol).any(1)
133
133
 
134
134
 
135
+ def grid_step(diffs: ArrayLike) -> float:
136
+ """Recover the fundamental channel step from the observed diffs.
137
+
138
+ On a regular grid with missing channels every diff is an integer multiple
139
+ of the channel width, so the gcd of the diffs recovers that width even when
140
+ no two surviving channels are adjacent -- the case where ``min(diffs)``
141
+ overestimates the step (e.g. present channels 0, 3, 5 give ``min == 2`` but
142
+ the true step is 1). Falls back to ``min(diffs)`` if the gcd is numerically
143
+ unstable or does not divide every diff, so the common case is unchanged (any
144
+ surviving adjacent pair makes the gcd equal the minimum diff exactly).
145
+
146
+ Args:
147
+ diffs (ArrayLike): Differences between consecutive spec values.
148
+
149
+ Returns:
150
+ float: Estimated regular grid step.
151
+ """
152
+ diffs = np.abs(np.asarray(diffs, dtype=np.longdouble))
153
+ min_diff = np.min(diffs)
154
+ tol = np.max(diffs) * 1e-6
155
+
156
+ g = diffs[0]
157
+ for d in diffs[1:]:
158
+ a, b = (g, d) if g >= d else (d, g)
159
+ # Tolerant Euclid: stop once the remainder is within the noise floor.
160
+ while b > tol:
161
+ a, b = b, a % b
162
+ g = a
163
+
164
+ divides_all = np.all(np.abs(np.round(diffs / g) - diffs / g) <= 1e-3)
165
+ if g <= tol or not divides_all:
166
+ return float(min_diff)
167
+ return float(g)
168
+
169
+
135
170
  def even_spacing(specs: u.Quantity, time_domain_mode: bool = False) -> SpequencyInfo:
136
171
  """Make the frequencies or times evenly spaced.
137
172
 
@@ -143,9 +178,9 @@ def even_spacing(specs: u.Quantity, time_domain_mode: bool = False) -> Spequency
143
178
  """
144
179
  specs_arr = specs.value.astype(np.longdouble)
145
180
  diffs = np.diff(specs_arr)
146
- min_diff: float = np.min(diffs)
147
- # Create a new array with the minimum difference
148
- new_specs = np_arange_fix(specs_arr[0], specs_arr[-1], min_diff)
181
+ step = grid_step(diffs)
182
+ # Create a new array with the fundamental grid step
183
+ new_specs = np_arange_fix(specs_arr[0], specs_arr[-1], step)
149
184
  missing_chan_idx = np.logical_not(
150
185
  isin_close(new_specs, specs_arr, time_domain_mode)
151
186
  )
@@ -83,6 +83,7 @@ addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"]
83
83
  xfail_strict = true
84
84
  filterwarnings = [
85
85
  "error",
86
+ "ignore:The TestRunner",
86
87
  ]
87
88
  log_cli_level = "INFO"
88
89
  testpaths = [
@@ -6,7 +6,7 @@ import astropy.units as u
6
6
  import numpy as np
7
7
  import pytest
8
8
  from astropy.io import fits
9
- from fitscube.combine_fits import combine_fits, parse_specs
9
+ from fitscube.combine_fits import combine_fits, even_spacing, parse_specs
10
10
 
11
11
 
12
12
  @pytest.fixture
@@ -108,3 +108,16 @@ def test_uneven_combine(
108
108
  assert chan in (1, 2)
109
109
  continue
110
110
  assert np.allclose(plane, image)
111
+
112
+
113
+ def test_even_spacing_non_adjacent_gaps():
114
+ # Present channels 0, 3, 5 GHz on a 1 GHz grid (1, 2, 4 missing). No two
115
+ # surviving channels are adjacent, so min(diffs) == 2 GHz would mis-grid the
116
+ # axis to [0, 2, 4] and drop the real channels. The gcd of the diffs
117
+ # [3, 2] GHz recovers the true 1 GHz step.
118
+ specs = np.array([0.0, 3.0, 5.0]) * u.GHz
119
+ new_specs, missing_chan_idx = even_spacing(specs)
120
+ assert len(new_specs) == 6, "should rebuild the full 0..5 GHz grid"
121
+ assert missing_chan_idx.sum() == 3
122
+ expected_missing = np.array([False, True, True, False, True, False])
123
+ assert np.array_equal(missing_chan_idx, expected_missing)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes